Some links on this page are affiliate links: if you buy through them we may earn a commission, at no extra cost to you.
The reliable fix is to give every PDF its own PdfWriter, PdfDocument and layout Document, write directly to a file or stream, and close that document before starting the next one. Do not retain completed iText objects, image byte arrays or output buffers. For large ordinary PDFs, enable immediate flushing where the document’s conformance requirements allow it; then measure the live heap, limit batch concurrency and only increase -Xmx after checking for retained references.
What the exception actually tells you
java.lang.OutOfMemoryError: Java heap space means the JVM could not satisfy an allocation in the Java heap. It does not by itself prove that iText 7 leaked memory. Two different conditions produce the same symptom:
- Peak-size pressure: one document, image, font, table or byte array needs more contiguous heap than the current limit allows.
- Retention: completed jobs remain reachable through a list, cache, thread-local, callback, buffer or unclosed iText object, so the live set rises from job to job.
Other messages require a different interpretation. GC overhead limit exceeded indicates that garbage collection is consuming most of the time with little memory reclaimed; Requested array size exceeds VM limit points to an oversized array request; native-memory wording concerns memory outside the Java heap. Capture the exact exception text before changing the design.
What’s actually slowing this PC down?
Pick the symptom - the matching free tool is one click away.
Use one complete iText lifecycle per output file
Create the writer, PDF document and layout document inside the loop that processes a job. The layout Document owns the normal close operation: closing it closes the associated PdfDocument. Keeping ownership inside a bounded scope makes it difficult for one result to retain the next job’s state.
import com.itextpdf.kernel.geom.PageSize;
import com.itextpdf.kernel.pdf.PdfDocument;
import com.itextpdf.kernel.pdf.PdfWriter;
import com.itextpdf.layout.Document;
import com.itextpdf.layout.element.Cell;
import com.itextpdf.layout.element.Paragraph;
import com.itextpdf.layout.element.Table;
import java.nio.file.Path;
import java.util.List;
public class BatchPdf {
public record Job(Path outputPath, String title, List<String> rows) {}
public static void main(String[] args) {
List<Job> jobs = List.of(
new Job(Path.of("out/one.pdf"), "First report", List.of("A", "B")),
new Job(Path.of("out/two.pdf"), "Second report", List.of("C", "D"))
);
for (Job job : jobs) {
try (PdfWriter writer = new PdfWriter(job.outputPath().toString());
PdfDocument pdf = new PdfDocument(writer);
Document doc = new Document(pdf, PageSize.A4, true)) {
addJobContent(doc, job);
}
}
}
private static void addJobContent(Document doc, Job job) {
doc.add(new Paragraph(job.title()));
Table table = new Table(1);
for (String row : job.rows()) {
table.addCell(new Cell().add(new Paragraph(row)));
}
doc.add(table);
}
}
The final true selects immediate flushing in the Document constructor. If your exact iText version does not make every wrapper safe for try-with-resources, keep the same ownership rule and close Document in a finally block. Consult that version’s API for its precise close behavior rather than assuming that closing only the writer releases layout state.
Do not accumulate completed results
- Write each PDF to its final path, an upload stream, or a temporary file that is handed off and then dereferenced.
- Avoid a
ByteArrayOutputStreamfor every job unless the caller genuinely needs all results in memory. If bytes are required, return one result, upload or persist it, clear the reference, then process the next job. - Release source image bytes, decoded image objects, JSON payloads and generated content collections at the end of each iteration.
- Do not put
Document,PdfDocument, writers or large input arrays in a results list for later cleanup.
Flush pages when the document rules permit it
Immediate flushing writes pages and page-related instructions as soon as possible, reducing the amount of completed layout state that remains live. It is most useful for long, ordinary PDFs generated sequentially.
Large tables
Build large tables incrementally instead of first materializing every row, cell value and image in application collections. Keep only the data needed for the current portion, add rows, and let iText write completed pages. Also avoid converting an entire database export to one giant list before generation; stream or page the query where practical.
Recommended Free Tools
Rank #2
PDF/A and PDF/UA exceptions
PDF/A and PDF/UA conformance checks can require pages to remain available until close. In those jobs, page flushing may be disabled or less effective because iText must perform document-wide validation. Reduce concurrency, limit other buffers and size the heap from measurements instead of copying a universal -Xmx value.
Complex layouts
Headers, footers, tagged structure, cross-references, late page-number fields and accessibility metadata can defer work. Test immediate flushing with representative documents, not just a small sample. A setting that works for a plain text report may not be appropriate for a conformance or heavily tagged document.
Control concurrency before increasing the heap
Each active PDF can hold layout state, fonts, images, indirect objects and input data. Running ten large jobs concurrently can therefore require roughly ten simultaneous working sets even when one job succeeds alone. Use a small, bounded executor and measure throughput as you increase it.
- Run one representative PDF and record its peak heap and post-GC live set.
- Run the same jobs sequentially. A sequential failure suggests document size, buffering or retention inside one job.
- Run the intended parallelism. A failure that appears only here points to simultaneous live documents or buffers.
- Set a queue limit so producers cannot enqueue unbounded source payloads while workers are busy.
- Delete temporary files and clear per-job references after a successful handoff or after recording a failure.
Do not call System.gc() as a production memory-management strategy. It may change timing without fixing a reachable object graph, and it can reduce throughput.
Diagnose with evidence, not guesses
1. Verify the effective JVM settings
Check the flags used by the actual launcher, service unit or container, not only your development IDE. Container limits and startup scripts often differ from local settings. Record -Xms, -Xmx, Java version, iText version, document page count and image sizes.
java -Xms512m -Xmx2g
-XX:+HeapDumpOnOutOfMemoryError
-XX:HeapDumpPath=/var/tmp/heap
-jar report-batch.jar
-XX:+HeapDumpOnOutOfMemoryError writes a heap dump when the failure occurs; -XX:HeapDumpPath selects its destination. Ensure the destination has enough disk space and suitable access controls because a dump can contain document data.
Rank #4
2. Compare the live set after full collections
Look at memory after a full garbage collection between jobs, using your normal profiler or production-safe GC logging. A rising baseline indicates retained references. A stable baseline followed by failure during one huge allocation indicates peak-size or array pressure instead.
3. Inspect dominators in the dump
Search for collections containing completed jobs, image byte arrays, caches, thread locals, callback queues and iText objects. The largest retained-size paths identify what is keeping memory reachable. Fix one ownership path at a time and repeat the measurement.
4. Change one variable per experiment
First close and release resources, then lower concurrency, then enable permitted flushing, then reduce image resolution or buffering, and only afterward adjust -Xmx. This sequence distinguishes a lifecycle defect from a legitimately large workload.
Best Value
Common symptoms and targeted fixes
| Symptom | Likely cause | Fix to try |
|---|---|---|
| Fails only after many successful PDFs | Completed documents, byte arrays or job objects remain reachable | Scope resources per iteration, close the layout document, clear collections and inspect heap-dump dominators |
| Fails on the first large PDF | One image, table, font or output buffer exceeds the available peak heap | Stream output, lower image resolution, page large inputs, enable permitted flushing and then reassess heap size |
| Sequential mode works; parallel mode fails | Too many simultaneous working sets | Use a bounded executor and reduce worker count |
| Failure appears only with PDF/A or PDF/UA | Conformance processing needs deferred page access | Expect less benefit from flushing, reduce concurrency and size from measured live-set data |
GC overhead limit exceeded |
GC is running constantly while reclaiming little | Find retained references and reduce the live set; do not treat a larger heap as the only remedy |
Requested array size exceeds VM limit |
A single array request is beyond the JVM’s maximum or practical size | Split the input, avoid whole-file byte-array conversions and stream data |
Choosing among the fixes
| Change | Peak-heap effect | Trade-off |
|---|---|---|
Close each Document promptly |
Removes completed layout state between jobs | Requires clear ownership and error-safe cleanup |
| Immediate/page flushing | Reduces retained completed pages when allowed | May be incompatible with PDF/A, PDF/UA or layouts needing deferred checks |
| Stream or persist output | Avoids one in-memory result per PDF | Requires downstream upload or file handling |
| Lower concurrency | Reduces simultaneous working sets | Can reduce throughput |
Increase -Xmx |
Raises the ceiling for legitimate peak allocations | Does not remove retained references and leaves less headroom for native memory |
Operational checklist
- Use separate writer, PDF and layout objects for every output.
- Close the layout document in all success and failure paths.
- Write directly to a destination instead of retaining every result as bytes.
- Release image data and application collections before the next job.
- Enable immediate flushing for ordinary documents after testing the exact layout.
- Assume PDF/A and PDF/UA may require deferred checks and retained pages.
- Bound parallelism and queue depth.
- Record Java and iText versions, JVM flags, page counts, image sizes and concurrency.
- Capture a heap dump on failure and inspect retained-size paths.
- Change one variable at a time, then compare peak and post-GC live memory.
Or skip the browser setup
If your PDF workflow also needs a screenshot of a web page for a cover, appendix or visual reference, ScreenshotNeo can return the image or PDF from one request instead of maintaining browser automation. Its cleanup step accepts consent banners and removes more than 60 known consent platforms, newsletter popups and chat widgets before capture. Bot checks, blank pages, timeouts, failed loads and cache hits are not billed, and response headers identify the page verdict and billing result.
It also provides an MCP server with take_screenshot, get_page_info and capture_pdf for Claude, Cursor and other MCP clients. The free plan includes 1,000 screenshots per month without a card; paid plans start at $5 for 3,000 screenshots. Every plan includes the available features.
For request options and authentication, see the ScreenshotNeo API documentation.
PC Slower Than It Used to Be?
A free scan shows the junk files, broken settings and background clutter dragging Windows down - then fixes them in one click.Free scan · Windows 10 & 11Outdated Drivers Are Slowing You Down
One free scan finds every outdated or missing driver and matches the right update for your exact hardware.Free scan · exact hardware matchcurl -G "https://api.screenshotneo.com/v1/shot" -d access_key=YOUR_API_KEY --data-urlencode url=https://stripe.com -o shot.webp
import requests
r = requests.get("https://api.screenshotneo.com/v1/shot", params={"access_key": "YOUR_API_KEY", "url": "https://stripe.com"}, timeout=90)
r.raise_for_status()
open("shot.webp", "wb").write(r.content)
const q = new URLSearchParams({ access_key: 'YOUR_API_KEY', url: 'https://stripe.com' });
const res = await fetch(`https://api.screenshotneo.com/v1/shot?${q}`);
Create a free ScreenshotNeo account to use the 1,000 monthly screenshots with no card.
Frequently Asked Questions
Should I close PdfWriter before Document?
No. Let the layout document finish first, because it owns the normal close operation for the associated PDF. If your iText release has different AutoCloseable behavior, follow that release’s API contract and keep one clearly defined owner.
Why can a heap dump contain sensitive report data?
The dump records live object contents, which can include text, image bytes and source payloads. Store it with the same access controls as the generated PDFs and delete it after analysis.
Is a larger heap always safer for a batch service?
No. A larger heap can postpone a failure caused by a retained reference and can reduce memory available for the JVM and native libraries. Measure the live set and peak allocation first.
Free tools Windows power users keep installed
One-click scans. No signup required.
Quick Recap
Product prices and availability are accurate as of the date/time indicated and are subject to change. Any price and availability information displayed on Amazon at the time of purchase will apply.

