ScreenshotNeo

BlogHTML to image & PDF

How to Add Custom Headers and Footers to PDFs in Java

Add repeating headers, footers, page numbers, and titles to Java PDFs with iText 7, iText 5, or Apache PDFBox.

By the ScreenshotNeo team29 September 20262 min read

How to Add Custom Headers and Footers to PDFs in Java

To add a custom header or footer to every PDF page in Java, render it from a page-level callback or event. In iText 7, register a START_PAGE handler for headers and an END_PAGE handler for footers. In iText 5, extend PdfPageEventHelper and implement onEndPage. In Apache PDFBox, append a PDPageContentStream to each page and draw at coordinates calculated from that page’s geometry.

Keep header and footer drawing outside the normal body flow, reserve top and bottom margins, and calculate positions from the page rectangle. Those three decisions prevent most overlap and rotation bugs.

Choose the right Java PDF approach

Situation Recommended API Why
Creating a new PDF with structured content iText 7 Page events work naturally with the layout engine, tables, fonts, and HTML conversion.
Maintaining an older iText application iText 5 PdfPageEventHelper is designed for repeated page decorations.
Adding marks to an existing PDF Apache PDFBox Append mode lets you write directly onto each existing page.
HTML to PDF with repeating decorations iText 7 plus pdfHTML The official event-handler pattern can surround converted HTML content.

Apache PDFBox is an open-source Java library for working with PDF documents. Review the official PDFBox documentation and confirm the current iText license for your deployment before choosing a commercial distribution.

iText 7: page events for a new PDF

iText 7 separates page decoration from document content. Register one handler at PdfDocumentEvent.START_PAGE and another at PdfDocumentEvent.END_PAGE. The header runs as a page starts; the footer runs as the page is finished. The same pattern is used in iText’s official HTML-to-PDF examples.

Page events keep repeating decorations separate from the document body.
Page events keep repeating decorations separate from the document body.

Complete example

import com.itextpdf.kernel.events.Event;
import com.itextpdf.kernel.events.IEventHandler;
import com.itextpdf.kernel.geom.Rectangle;
import com.itextpdf.kernel.pdf.PdfDocument;
import com.itextpdf.kernel.pdf.PdfDocumentEvent;
import com.itextpdf.kernel.pdf.PdfPage;
import com.itextpdf.kernel.pdf.PdfWriter;
import com.itextpdf.kernel.pdf.canvas.PdfCanvas;
import com.itextpdf.layout.Canvas;
import com.itextpdf.layout.Document;
import com.itextpdf.layout.element.Paragraph;
import com.itextpdf.layout.properties.TextAlignment;

public class IText7HeaderFooter {
    static class Header implements IEventHandler {
        private final String text;
        Header(String text) { this.text = text; }
        public void handleEvent(Event event) {
            PdfDocumentEvent e = (PdfDocumentEvent) event;
            PdfDocument pdf = e.getDocument();
            PdfPage page = e.getPage();
            Rectangle box = page.getPageSize();
            PdfCanvas pdfCanvas = new PdfCanvas(page);
            try (Canvas canvas = new Canvas(pdfCanvas, box)) {
                canvas.showTextAligned(new Paragraph(text).setFontSize(9),
                    box.getWidth() / 2, box.getTop() - 24,
                    TextAlignment.CENTER);
            }
        }
    }

    static class Footer implements IEventHandler {
        public void handleEvent(Event event) {
            PdfDocumentEvent e = (PdfDocumentEvent) event;
            PdfPage page = e.getPage();
            Rectangle box = page.getPageSize();
            PdfCanvas pdfCanvas = new PdfCanvas(page);
            try (Canvas canvas = new Canvas(pdfCanvas, box)) {
                String label = "Page " + e.getDocument().getPageNumber(page);
                canvas.showTextAligned(new Paragraph(label).setFontSize(9),
                    box.getWidth() / 2, box.getBottom() + 18,
                    TextAlignment.CENTER);
            }
        }
    }

    public static void main(String[] args) throws Exception {
        PdfWriter writer = new PdfWriter("report.pdf");
        PdfDocument pdf = new PdfDocument(writer);
        pdf.addEventHandler(PdfDocumentEvent.START_PAGE,
            new Header("Acme quarterly report"));
        pdf.addEventHandler(PdfDocumentEvent.END_PAGE, new Footer());

        Document document = new Document(pdf);
        document.setMargins(48, 36, 48, 36);
        for (int i = 1; i <= 8; i++) {
            document.add(new Paragraph("Section " + i)
                .setFontSize(18));
            document.add(new Paragraph(
                "Body content is laid out inside the reserved margins. "
                + "Add enough text here to create multiple pages."));
        }
        document.close();
    }
}

See the iText page-events guidance for the event model. The handler uses the page rectangle rather than hard-coded Letter dimensions, so the same code can work with A4 or a custom page size.

Use the handler's Canvas to add a paragraph, line, image, or table. Draw a thin rule below a header by calling moveTo, lineTo, and stroke on PdfCanvas. For a logo, load an ImageData object and place it relative to box.getLeft() and box.getTop(). Keep the image height within the reserved header margin.

“Page X of Y” numbering

The current page number is available while an event runs. The final page count is not known until layout finishes, so total-page numbering needs a deferred placeholder and a second pass. The official iText example creates a placeholder for the total, writes the current page in the end-page event, and fills the placeholder after the document has been closed.

iText 5: PdfPageEventHelper

For iText 5, extend PdfPageEventHelper, draw with the writer's direct content canvas, and register the event with writer.setPageEvent. Do not call Document.add() from a page event; the iText API specifically cautions against adding normal document content there.

import com.itextpdf.text.Document;
import com.itextpdf.text.DocumentException;
import com.itextpdf.text.Element;
import com.itextpdf.text.Phrase;
import com.itextpdf.text.pdf.ColumnText;
import com.itextpdf.text.pdf.PdfPageEventHelper;
import com.itextpdf.text.pdf.PdfWriter;
import java.io.FileOutputStream;

public class IText5HeaderFooter {
    static class HeaderFooter extends PdfPageEventHelper {
        @Override
        public void onEndPage(PdfWriter writer, Document document) {
            float center = (document.left() + document.right()) / 2;
            ColumnText.showTextAligned(writer.getDirectContent(),
                Element.ALIGN_CENTER, new Phrase("Acme report"),
                center, document.top() + 18, 0);
            ColumnText.showTextAligned(writer.getDirectContent(),
                Element.ALIGN_CENTER,
                new Phrase("Page " + writer.getPageNumber()),
                center, document.bottom() - 18, 0);
        }
    }

    public static void main(String[] args) throws Exception {
        Document document = new Document();
        PdfWriter writer = PdfWriter.getInstance(document,
            new FileOutputStream("report-itext5.pdf"));
        writer.setPageEvent(new HeaderFooter());
        document.setMargins(36, 36, 54, 54);
        document.open();
        document.add(new Phrase("Report body content."));
        document.newPage();
        document.add(new Phrase("Second page."));
        document.close();
    }
}

The larger top and bottom margins leave room for the decorations. If your header contains a two-column table, use writeSelectedRows and keep its width inside document.left() and document.right().

Apache PDFBox: add headers to an existing PDF

PDFBox is a good fit when the pages already exist. Open the file, iterate through PDPage objects, append a content stream, write the text, and save a new file. Append mode preserves existing page content while adding the decoration.

import java.nio.file.Path;
import org.apache.pdfbox.Loader;
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;

public class PdfBoxHeaderFooter {
    public static void main(String[] args) throws Exception {
        Path input = Path.of("input.pdf");
        Path output = Path.of("output-with-footer.pdf");
        try (PDDocument doc = Loader.loadPDF(input.toFile())) {
            PDType1Font font = new PDType1Font(
                Standard14Fonts.FontName.HELVETICA);
            int pageNo = 1;
            for (PDPage page : doc.getPages()) {
                PDRectangle box = page.getMediaBox();
                float width = box.getWidth();
                float height = box.getHeight();
                try (PDPageContentStream cs = new PDPageContentStream(
                        doc, page,
                        PDPageContentStream.AppendMode.APPEND,
                        true, true)) {
                    cs.beginText();
                    cs.setFont(font, 9);
                    cs.newLineAtOffset(36, height - 24);
                    cs.showText("Acme report");
                    cs.endText();

                    cs.beginText();
                    cs.setFont(font, 9);
                    String footer = "Page " + pageNo;
                    float footerWidth = font.getStringWidth(footer) / 1000 * 9;
                    cs.newLineAtOffset(width - 36 - footerWidth, 18);
                    cs.showText(footer);
                    cs.endText();
                }
                pageNo++;
            }
            doc.save(output.toFile());
        }
    }
}

Read the Apache PDFBox project documentation for version-specific APIs. For rotated pages, account for the page rotation and effective width and height before choosing coordinates. A visible footer on an unrotated page can appear along a side when the page has a 90- or 270-degree rotation.

Preventing overlap with body content

  1. Reserve space first. Set top and bottom margins at least as large as the decoration plus a safety gap.
  2. Measure the real content. A two-line header, large logo, or wrapped title needs more space than a nine-point single line.
  3. Keep event coordinates outside the body rectangle. In iText, use document.top() and document.bottom() plus an offset. In PDFBox, use media-box dimensions.
  4. Test the longest strings. A translated title can be much wider than the English version.
  5. Inspect the first and last page. First-page margins, page breaks, and rotated pages often differ.

For existing PDFs, a footer can still overlap content because PDFBox does not reflow the original page. If the source already uses the bottom margin, either place the footer in a reserved blank area or rebuild the document with a layout library.

Headers and footers for HTML-to-PDF

When HTML is converted with iText pdfHTML, register the same start-page and end-page handlers on the PdfDocument before converting the HTML. Keep the CSS page margins aligned with the event-handler coordinates. This avoids a situation where HTML content occupies the area reserved for the Java-drawn header.

Common errors and fixes

Symptom Likely cause Fix
Header is hidden behind body text Top margin is too small. Increase the document's top margin and move the event text into the reserved band.
Footer appears on the side Page rotation was ignored. Transform coordinates using the page rotation and effective dimensions.
Only the first page has a footer Code draws once after document creation. Register a page event or iterate over every page.
Total page count is blank Placeholder was never filled after layout. Use deferred total-page handling or run a second pass.
PDFBox output is corrupted The content stream was not closed, or append mode was not used. Use try-with-resources and AppendMode.APPEND, then save after all pages are processed.
Text is clipped Coordinate is outside the media box or font metrics were ignored. Calculate width with the selected font and keep coordinates inside the page rectangle.
iText throws a licensing or dependency error Missing module or incompatible version. Align kernel/layout/pdfHTML versions and review the current license terms.
Header overlaps an image Absolute drawing does not change existing content flow. Reserve the space during layout or place the decoration in an unused area.
Choose layout events for new documents and append mode for existing pages.
Choose layout events for new documents and append mode for existing pages.

Performance, reliability, and cost considerations

Page events add a small amount of drawing work per page, but the dominant cost is usually layout, font embedding, image decoding, or HTML conversion. Reuse font objects, avoid loading the same logo for every page, and stream large documents instead of retaining unnecessary application data. For PDFBox, process pages sequentially and close each content stream promptly.

For reliable output, write to a temporary destination and move it into place only after save succeeds. Keep the source file unchanged when modifying existing PDFs. Add automated checks for page count, expected metadata, and the presence of header text in representative pages. If documents can contain untrusted input, isolate conversion work and apply the library's current security guidance.

Library licensing is a deployment cost. PDFBox is Apache-licensed; iText licensing depends on how you distribute and use it, so confirm current terms with iText before shipping.

Or skip the browser setup

If your Java service needs a PDF or image of a live webpage rather than a PDF assembled from Java content, ScreenshotNeo provides a single request to capture a URL. Its PDF options include paper size, margins, landscape mode, and page ranges. Cookie and consent banners are accepted and removed before capture, along with more than 60 known consent platforms, newsletter popups, and chat widgets; each cleanup step can be turned off. Bot checks, blank pages, timeouts, failed loads, and cache hits are not billed, and the response identifies the result with X-Page-Verdict and X-Billed headers. An MCP server lets Claude, Cursor, and other MCP clients call take_screenshot, get_page_info, and capture_pdf.

See the ScreenshotNeo API documentation for all options.

cURL

curl -G "https://api.screenshotneo.com/v1/shot" -d access_key=YOUR_API_KEY --data-urlencode url=https://stripe.com -o shot.webp

Python

import requests
r = requests.get("https://api.screenshotneo.com/v1/shot", params={"access_key": "YOUR_API_KEY", "url": "https://stripe.com"}, timeout=90)
open("shot.webp", "wb").write(r.content)

Node.js

const q = new URLSearchParams({ access_key: 'YOUR_API_KEY', url: 'https://stripe.com' });
const res = await fetch(`https://api.screenshotneo.com/v1/shot?${q}`);

ScreenshotNeo has a free plan with 1,000 screenshots per month and no card required. Paid plans start at $5 for 3,000 shots; every feature is available on every plan. Create a free ScreenshotNeo account.

Practical checklist

  • Choose an event or page-append approach appropriate to new versus existing PDFs.
  • Reserve top and bottom margins before drawing.
  • Use page rectangles instead of assuming Letter size.
  • Handle page rotation and multilingual text.
  • Use deferred placeholders for total-page counts.
  • Close canvases and save only after all pages are processed.
  • Validate first, middle, last, and rotated pages.

FAQ

Can I use the same handler for every page size?

Yes. Read each page's rectangle and calculate positions from its width and height. Do not hard-code Letter coordinates.

Can a page event change document layout?

No. A page event draws on a page; it does not reflow body content. Set margins or layout properties before adding content.

How do I add a different first-page header?

Check the page number inside the handler and select a first-page variant, or register separate logic that branches for page one.

Can PDFBox create a new document with headers?

Yes, but its low-level coordinate API requires you to manage text flow, page breaks, and margins yourself. iText's layout model is usually more convenient for structured new documents.