PDFBox 3 in Java: Line Breaks, Page Breaks and Unicode

2026-11-03来源 dev.to JavaEnglish

A 60-row statement drawn with Apache PDFBox needs Java code to decide where page 1 ends, and a cover letter needs Java code to decide where each line breaks. Apache PDFBox is the Apache Software Foundation's PDF library for Java, and its 3.0.8 release has been on Maven Central since 11 July 2026. We wrote this for teams producing documents with it, and it covers the issues that show up once the first real templates go through it, each one with run output or the project's own documentation behind it.

We work at Iron Software, and the end of this article covers our library, IronPDF.

Who Decides Where a Line Breaks?

The calling code does. PDFBox accepts neither HTML nor CSS. The project FAQ calls it low-level, offering text and image drawing but no API above that for laying out pages or flowing paragraphs. A paragraph is therefore a list of lines the program builds itself by measuring words with getStringWidth, which returns widths in 1/1000 units of text space.

import org.apache.pdfbox.pdmodel.PDDocument; import org.apache.pdfbox.pdmodel.PDPage; import org.apache.pdfbox.pdmodel.PDPageContentStream; import org.apache.pdfbox.pdmodel.common.PDRectangle; import org.apache.pdfbox.pdmodel.font.PDType0Font; import java.io.File; import java.io.IOException; import java.util.ArrayList; import java.util.List; public class DrawWrappedLetter { public static void main(String[] args) throws IOException { String body = "Thank you for your order. The parcel left our warehouse in Gdańsk this morning " + "and should reach you within three working days. Reply to this letter if any item is missing."; try (PDDocument document = new PDDocument()) { PDPage page = new PDPage(PDRectangle.LETTER); document.addPage(page); PDType0Font font = PDType0Font.load(document, new File("fonts/NotoSans-Regular.ttf")); float fontSize = 12; float margin = 72; float maxWidth = page.getMediaBox().getWidth() - 2 * margin; // Each showText call draws one line, so the program measures words and decides the breaks itself. List<String> lines = new ArrayList<>(); String line = ""; for (String word : body.split(" ")) { String candidate = line.isEmpty() ? word : line + " " + word; if (font.getStringWidth(candidate) / 1000 * fontSize > maxWidth) { lines.add(line); line = word; } else { line = candidate; } } lines.add(line); try (PDPageContentStream content = new PDPageContentStream(document, page)) { content.beginText(); content.setFont(font, fontSize); content.setLeading(fontSize * 1.4f); // PDF coordinates start at the bottom left, so the first baseline sits one margin below the top edge. content.newLineAtOffset(margin, page.getMediaBox().getHeight() - margin); for (String text : lines) { content.showText(text); content.newLine(); } content.endText(); } document.save("letter.pdf"); System.out.println("Lines: " + lines.size()); } } } 

It printed Lines: 3 and saved a 7,336-byte letter.pdf on a 612 by 792 point US Letter page, the paragraph broken into three lines inside one-inch margins. A dozen lines of Java do the job a browser does for a <p> element, and they handle only plain words, with no hyphenation, justification, bold runs or links. For richer layouts the FAQ sends readers elsewhere, to Apache FOP when the source is XML, or to add-on layout projects like Boxable and easytable that sit on top of PDFBox.

Page Breaks Are Tracked by Hand

The same applies one level up. newLine moves the baseline down by the leading and nothing more, so a table longer than the page keeps drawing below the bottom edge unless the program opens a new page itself.

import org.apache.pdfbox.pdmodel.PDDocument; import org.apache.pdfbox.pdmodel.PDPage; import org.apache.pdfbox.pdmodel.PDPageContentStream; import org.apache.pdfbox.pdmodel.common.PDRectangle; import org.apache.pdfbox.pdmodel.font.PDType1Font; import org.apache.pdfbox.pdmodel.font.Standard14Fonts; import java.io.IOException; public class ManualPagination { public static void main(String[] args) throws IOException { float margin = 50; float leading = 16; PDType1Font font = new PDType1Font(Standard14Fonts.FontName.HELVETICA); try (PDDocument document = new PDDocument()) { PDPageContentStream content = null; float y = 0; for (int row = 1; row <= 60; row++) { // The program tracks the baseline itself and opens a new page once it has dropped below the bottom margin. if (content == null || y < margin) { if (content != null) { content.endText(); content.close(); } PDPage page = new PDPage(PDRectangle.A4); document.addPage(page); content = new PDPageContentStream(document, page); content.beginText(); content.setFont(font, 11); content.setLeading(leading); y = page.getMediaBox().getHeight() - margin; content.newLineAtOffset(margin, y); } else { content.newLine(); y -= leading; } content.showText("Line " + row + " " + String.format("%.2f", row * 1.25)); } // Each page's content stream has to be closed before the document is saved. content.endText(); content.close(); document.save("paginated-statement.pdf"); System.out.println("Pages: " + document.getNumberOfPages()); } } } 

The run printed Pages: 2, with rows 1 to 48 on the first A4 page and rows 49 to 60 on the second, all inside the page bounds. Repeating a header row, keeping a row's cells together or adding a page number would each be more code of the same kind. Without the y < margin check, rows keep going below the bottom edge of the first page, still present in the content stream and invisible in a viewer.

Names Like Łódź and Gdańsk Need PDType0Font

The letter above loads Noto Sans through PDType0Font.load for a reason. With the Standard 14 Helvetica, which needs no font file and is created in 3.x through new PDType1Font(Standard14Fonts.FontName.HELVETICA), showText on the word Gdańsk threw an IllegalArgumentException reporting that U+0144 ('nacute') is not available in Helvetica's WinAnsiEncoding, and Łódź failed the same way on U+0141. The FAQ's fix for that message is the Type 0 font, which PDType0Font.load(PDDocument, File) embeds as a subset of the TrueType file. Any service printing customer names or addresses with letters like these has to ship and load font files for each script it needs.

What Moving From 2.x to 3.x Changes

The 3.0 migration guide lists the API changes that stop 2.x code compiling. PDDocument lost its load methods to the new Loader class, the Standard 14 fonts lost their static fields, and a StreamCacheCreateFunction took the place of the MemoryUsageSetting parameter. Merging shows the new signature.

import org.apache.pdfbox.io.IOUtils; import org.apache.pdfbox.multipdf.PDFMergerUtility; import java.io.File; import java.io.IOException; public class MergeInvoicePack { public static void main(String[] args) throws IOException { PDFMergerUtility merger = new PDFMergerUtility(); merger.addSource(new File("invoice.pdf")); merger.addSource(new File("statement.pdf")); merger.setDestinationFileName("invoice-pack.pdf"); // The stream cache argument arrived in 3.x, and a memory-only cache suits small inputs. merger.mergeDocuments(IOUtils.createMemoryOnlyStreamCache()); } } 

With a one-page invoice and a three-page statement as sources, it wrote a four-page, 10,381-byte invoice-pack.pdf. IOUtils lives in the separate pdfbox-io module that 3.0 introduced, which Maven pulls in with pdfbox alongside fontbox and commons-logging 1.4.0. A 2.x codebase has to rewrite every PDDocument.load call, font field and memory setting before it compiles against 3.0.8, or stay on the 2.0 line, which published 2.0.37 to Maven Central on 15 July 2026.

Security Advisories by Version

Ten CVE records appear on the PDFBox security page, and a fixed release exists for each. Scores are the ones NVD displays, from NIST for the older records and from CISA's ADP program for the 2026 pair.

  • CVE-2026-33929, CVSS 4.3 from CISA ADP: A path traversal in the ExtractEmbeddedFiles example and an incomplete fix for CVE-2026-23907, affects 2.0.24 to 2.0.36 and 3.0.0 to 3.0.7 and is fixed in 2.0.37 and 3.0.8.
  • CVE-2026-23907, CVSS 5.3 from CISA ADP: The original path traversal in that example, affects 2.0.24 to 2.0.35 and 3.0.0 to 3.0.6 and is fixed in 2.0.36 and 3.0.7.
  • CVE-2019-0228, CVSS 9.8 from NIST: XML external entity processing through a crafted XFDF file in 2.0.14, fixed in 2.0.15.
  • CVE-2016-2175, CVSS 7.8 from NIST: XML external entity processing through a crafted PDF in 1.8.0 to 1.8.11 and 2.0.0, fixed in 1.8.12 and 2.0.1.
  • CVE-2018-8036 and CVE-2018-11797, CVSS 6.5 and 5.5 from NIST: A parser loop and a long page-tree computation on crafted files, fixed in 2.0.11 and 2.0.12.
  • CVE-2021-27807, CVE-2021-27906, CVE-2021-31811 and CVE-2021-31812, CVSS 5.5 each from NIST: Infinite loops and out-of-memory errors on crafted files, fixed in 2.0.23 for the first two and 2.0.24 for the last two.

The project footnotes the 2026 pair as affecting only the examples module, but the NVD record for the later one lists the main apache:pdfbox CPE as vulnerable below 2.0.37 and 3.0.8, which is why NVD-based scanners raise it on the core artifact. Upgrading pdfbox to 3.0.8 or 2.0.37 clears all ten, and code copied from ExtractEmbeddedFiles needs the change from pull request 427. Maven Central has no fixed build of pdfbox-examples, though, since that artifact ends at 3.0.7 and 2.0.36. One more point from the same page affects upload services. Under the project's policy, crafted files that throw unchecked exceptions, loop without end or use memory out of proportion fall outside its security scope, and applications reading untrusted PDFs in volume are expected to set their own timeouts and memory caps.

Rendering the Statement From HTML Instead

Teams whose letters and statements already live as HTML can hand them to IronPDF for Java, Iron Software's commercial PDF library, which takes the layout and font work above off the Java side. It renders HTML with a full Chromium engine, so line breaks, table widths and page breaks follow the markup and its CSS, a TrueType file named in an @font-face rule of an HTML file embedded in our 2026.10.1 run with Łódź and Gdańsk intact, and page JavaScript runs when setEnableJavaScript is on, with setWaitFor delaying the render for a set time or a network-idle signal. The calls are PdfDocument.renderHtmlFileAsPdf for an HTML file or renderHtmlAsPdf for a string, then PdfDocument.merge, extractAllText or getSecurity() on the result before saveAs.

The Maven coordinate is com.ironsoftware:ironpdf, and Maven Central lists ten 2026 releases of it up to 2026.10.1 from 1 October 2026. A free trial key covers a test against your own templates, and the generation settings example walks through the render options.

The PDFBox Checklist in Short

PDFBox 3.0.8 leaves line wrapping, page breaks and Unicode font loading to the calling code, takes no HTML or CSS, asks 2.x code to be rewritten for its 3.x API and carries ten CVE records that 3.0.8 and 2.0.37 fix. For teams with HTML templates, we point to IronPDF for Java as the solution, with guides to merging PDFs and filling PDF forms on the same document object.

What reached your team first with PDFBox, the layout code, the fonts or the scanner report?

Apache PDFBox, PDFBox and Apache FOP are trademarks of The Apache Software Foundation, we have no affiliation with the foundation, and the details here rest on the project's own pages and on NVD and Maven Central records.

Drafted with AI. Code run and claims checked by the Iron Software DevRel team against Apache PDFBox 3.0.8, with IronPDF method names checked against com.ironsoftware:ironpdf 2026.10.1, on 2 November 2026.

原文:https://www.a8q9.cn/en/article/pdfbox-3-in-java-line-breaks-page-breaks-and-unicode-a01b4f.html

相关文章