PDFPipe

Migrating from Apache PDFBox / An imperative drawing API

Migrating off PDFBox, the lowest-level starting point there is

A Java library that operates close to the PDF format itself, where producing a document means writing content streams by hand and there is no layout engine at all.

What ports and what does not

The data layer, and nothing else, more completely than any other migration here. PDFBox is not a document library with a layout model: creating a page means constructing a content stream, beginning a text block, setting a font, moving to a position and showing a string. There is no wrapping, no flow and no pagination unless you wrote it, which means the old code contains a small layout engine somebody built by hand. That code is being deleted rather than ported, which is usually the point.

What the call becomes

PDDocument, PDPage and PDPageContentStream become a POST to /v1/pdf with html. The page's MediaBox becomes options.format. There are no margins to translate because PDFBox had none: the margins exist as constants in the positioning arithmetic, and those constants are what your CSS margins should be. Everything else, the beginText and endText pairs, the newLineAtOffset calls, the manual line wrapping, disappears.

Side by side

The old shape and the new one, with the parts that have no counterpart called out in comments rather than quietly omitted.

java
// Before
PDDocument doc = new PDDocument();
PDPage page = new PDPage(PDRectangle.A4);
doc.addPage(page);
try (PDPageContentStream cs = new PDPageContentStream(doc, page)) {
    cs.beginText();
    cs.setFont(PDType1Font.HELVETICA_BOLD, 18);
    cs.newLineAtOffset(40, 780);          // 40 from left, 780 from bottom
    cs.showText("Invoice");
    cs.endText();
}
doc.save(out);

// After
String body = mapper.writeValueAsString(Map.of(
    "html", html,
    "options", Map.of(
        "format", "A4",
        "margin", Map.of("top", "40pt", "bottom", "40pt",
                         "left", "40pt", "right", "40pt"))
));

/* The coordinate system is the thing to understand before transcribing.
   PDFBox measures from the bottom-left of the page; CSS measures from
   the top-left. A y of 780 on an A4 page, which is about 842 points
   tall, is roughly 62 points from the top. Every vertical constant in
   the old code needs that conversion, and getting it wrong produces a
   document that looks plausible and is positioned wrongly throughout.

   The manual line-wrapping code, if there is any, is deleted rather
   than converted. */

The CSS delta, in both directions

What the old engine accepted that a current one does not, and what it refused that a current one wants. The second direction is the one people forget, and it is where the value of the migration is.

  • Nothing to migrate. The document had no styling beyond font and size calls, and everything about its appearance is being specified for the first time.
  • The coordinate origin differs: PDFBox measures up from the bottom-left and CSS measures down from the top-left, so every vertical constant converts rather than transcribes.
  • Any hand-written line wrapping, column balancing or pagination logic is deleted. That code is frequently the largest part of a PDFBox document module and it has no replacement because the replacement is the layout engine.
  • Text measurement calls, used to decide where to break, disappear for the same reason.

What changes without anything erroring

The dangerous list. Each of these produces a different document and no diagnostic, so none of them are caught by a test that only checks the render succeeded.

  • Everything visual, because it is a rewrite.
  • Page count, since pagination was hand-written and is now automatic.
  • Anything the manual wrapping did specially, such as truncation rules or a custom break policy, which needs stating in CSS or accepting as changed.
  • Fonts, from PDFBox's font objects to @font-face.

What to diff before cutting over

Diff extracted text, and pay particular attention to anything the old code truncated, because hand-written wrapping usually truncated somewhere and the automatic version will not.

The one thing that always breaks

The coordinate conversion. Every vertical position in the old code is measured from the bottom of the page, every CSS length is measured from the top, and the conversion is a subtraction people do correctly for the first few constants and then stop doing. The result is a document that renders, looks broadly right, and has its blocks in the wrong places by amounts that vary down the page.

Frequently asked

Do I have to rewrite my templates to leave Apache PDFBox?

Effectively yes, and it is better to plan for that than to discover it. The old tool did not have HTML templates to port, so the document has to be expressed as markup for the first time. The value in the old code is the data layer underneath the drawing, and that part survives untouched.

Can I run both for a while?

Yes, and it is the safest way to do it. Put both behind one internal function that takes your data and returns bytes, switch on an environment variable, and run the new path on real traffic while the old one still serves. You get a diff on real documents rather than on fixtures, and you keep a way back that does not involve a deploy.

What about the documents already generated?

Nothing here changes them. They are files that already exist. What is worth deciding before the cutover is whether a regenerated document has to match the original byte for byte or merely say the same thing, because for anything with a legal or audit character the answer is usually to keep the original file rather than to be able to reproduce it.

Other migrations

The nearest neighbours first, then others that started from the same kind of tool, because the model matters more than the language.

Render one of your existing documents through the playground before changing any code. That comparison is the whole of the risk assessment for this migration.