How-to guides/Errors & Debugging
java.lang.OutOfMemoryError
Java heap space

How to fix iText OutOfMemoryError: Java heap space

iText loads entire PDF documents into JVM heap memory before processing. On large files or under high concurrency, this exhausts available heap and throws OutOfMemoryError. This guide covers JVM tuning, streaming alternatives, and a migration path to a memory-efficient Rust solution.

Why this happens

Large PDFs loaded fully into heap

iText's default document model reads the entire PDF into memory at once. A 100 MB file may require 300–500 MB of heap after object graph expansion, because every page, font, and image stream is parsed and held simultaneously.

iText buffers the entire document during write

When creating or modifying PDFs, iText accumulates all content in-memory until PdfDocument.close() is called. For documents with many pages or large embedded images, this buffer grows unboundedly and can trigger OOM before the file is written.

JVM heap configured too low for production workloads

Default JVM heap is typically 256 MB or 512 MB. Production servers processing concurrent PDF requests can exhaust this quickly. The problem compounds when multiple requests arrive before GC can reclaim memory from completed operations.

How to fix it

1

Increase JVM heap with -Xmx

Set the maximum heap size high enough for your largest expected document. As a rule of thumb, allocate 5× the size of the largest PDF you process. Add these flags to your JVM startup arguments.

# In your startup script or JAVA_OPTS:
java -Xms512m -Xmx4g -jar your-app.jar

# For Docker, set in your Dockerfile or compose:
ENV JAVA_OPTS="-Xms512m -Xmx4g"
2

Use iText's streaming API for large documents

For document creation, use PdfWriter with a SmartModeWriter or write pages incrementally and flush them. For reading, use PdfReader with partial loading where possible to avoid pulling the entire document into memory.

// Use try-with-resources and flush pages as you go
try (PdfWriter writer = new PdfWriter(dest);
     PdfDocument pdf = new PdfDocument(writer);
     Document document = new Document(pdf)) {

    for (int i = 0; i < pageCount; i++) {
        document.add(buildPage(i));
        // Flush completed pages to disk
        pdf.flushCopiedObjects(sourcePdf);
    }
}
3

Migrate to PDFluent for zero-copy streaming

PDFluent is built in Rust and uses a streaming parser that never loads the full document into memory. Pages are processed one at a time and dropped immediately. There is no JVM, no GC pauses, and no heap to tune.

use pdfluent::PdfDocument;

// PDFluent streams pages — no full document in memory
let doc = PdfDocument::open("large.pdf")?;

for page in doc.pages() {
    let page = page?;
    let text = page.text()?;
    println!("{}", text);
    // page is dropped here, memory reclaimed immediately
}

Frequently asked questions