spring-projects/spring-ai · error · IllegalStateException
Unreasonable pdf paragraph depth
Error message
Unreasonable pdf paragraph depth
What it means
ParagraphManager.flatten() recursively flattens the PDF outline-derived paragraph tree into a flat list. If the nesting depth exceeds REASONABLE_DEPTH, the tree is considered pathologically deep (likely malformed or malicious input) and an IllegalStateException is thrown instead of risking stack overflow or unbounded recursion.
Source
Thrown at document-readers/spring-ai-pdf-document-reader/src/main/java/org/springframework/ai/reader/pdf/config/ParagraphManager.java:87
this.document.getDocumentCatalog().getDocumentOutline(), 0, new HashSet<>());
}
catch (Exception e) {
throw new RuntimeException(e);
}
}
public List<Paragraph> flatten() {
List<Paragraph> paragraphs = new ArrayList<>();
for (var child : this.rootParagraph.children()) {
flatten(child, paragraphs, 0);
}
return paragraphs;
}
private void flatten(Paragraph current, List<Paragraph> paragraphs, int depth) {
if (depth > REASONABLE_DEPTH) {
throw new IllegalStateException("Unreasonable pdf paragraph depth");
}
paragraphs.add(current);
for (var child : current.children()) {
flatten(child, paragraphs, depth + 1);
}
}
/**
* For given {@link PDOutlineNode} bookmark convert all sibling {@link PDOutlineItem}
* items into {@link Paragraph} instances under the parentParagraph. For each
* {@link PDOutlineItem} item, recursively call
* {@link ParagraphManager#generateParagraphs} to process its children items.
* @param parentParagraph Root paragraph that the bookmark sibling items should be
* added to.
* @param bookmark TOC paragraphs to process.
* @param level Current TOC deepness level.
* @param visited used to prevent infinite recursion
* @return Returns a tree of {@link Paragraph}s that represent the PDF document TOC.View on GitHub (pinned to 98a7beda4f)
Solutions
- Inspect the PDF's bookmark/outline structure (e.g. with PDFBox) and flatten or normalize overly deep nesting before feeding it to the reader.
- Re-generate the PDF with a flattened outline (e.g. print-to-PDF or a tool that removes/relevels bookmarks).
- If deep nesting is legitimate, adjust REASONABLE_DEPTH or preprocess the outline yourself rather than relying on this reader.
- Catch IllegalStateException and reject/skip the offending document in batch pipelines.
Example fix
// before
DocumentReader reader = new ParagraphPdfDocumentReader(resource);
List<Document> docs = reader.get(); // throws on deep outline
// after
List<Document> docs;
try {
docs = new ParagraphPdfDocumentReader(resource).get();
} catch (IllegalStateException e) {
// fall back to page-based reader or sanitize outline first
docs = new PagePdfDocumentReader(resource).get();
} Defensive patterns
Strategy: try-catch
Validate before calling
try (PDDocument doc = Loader.loadPDF(file)) {
PDOutlineItem root = doc.getDocumentCatalog().getDocumentOutline() != null
? doc.getDocumentCatalog().getDocumentOutline().getFirstChild() : null;
int maxDepth = 0;
for (PDOutlineItem it = root; it != null; it = it.getNextSibling()) {
int d = 1; PDOutlineItem c = it.getFirstChild();
Deque<Object[]> stack = new ArrayDeque<>();
if (c != null) stack.push(new Object[]{c, 1});
while (!stack.isEmpty()) {
Object[] e = stack.pop();
PDOutlineItem cur = (PDOutlineItem) e[0]; int lvl = (Integer) e[1];
maxDepth = Math.max(maxDepth, lvl);
for (PDOutlineItem ch = cur.getFirstChild(); ch != null; ch = ch.getNextSibling())
stack.push(new Object[]{ch, lvl + 1});
}
}
if (maxDepth > 100) throw new IllegalStateException("outline too deep: " + maxDepth);
} Try / catch
try {
List<Document> docs = new ParagraphPdfDocumentReader(resource).get();
} catch (IllegalStateException e) {
if (e.getMessage().contains("Unreasonable pdf paragraph depth")) {
docs = new PagePdfDocumentReader(resource).get(); // fallback
} else throw e;
} Prevention
- Sanitize untrusted PDFs (strip or flatten outlines) before paragraph-based extraction
- Prefer PagePdfDocumentReader when you don't need outline-derived paragraph structure
- Fence deep-outline documents out at ingest with a PDFBox depth scan
- Handle all DocumentReader.get() IllegalStateExceptions in batch pipelines
When it happens
Trigger: Calling DocumentReader.load()/ParagraphManager.getParagraphs() on a PDF whose outline (bookmarks) hierarchy nests deeper than REASONABLE_DEPTH; the exception is raised during the recursive flatten(child, paragraphs, depth + 1) walk.
Common situations: Processing untrusted or auto-generated PDFs with deeply nested bookmark trees; documents produced by tools that emit thousands of nested outline entries; adversarial PDFs crafted to cause deep recursion (DoS).
Understand the failure class
Background: "This is a bug, please report it": internal invariant violations, unreachable panics, and SNH errors explained — this error's family across 47 libraries.
Related errors
- pdf containing circular reference or unreasonable nesting le
- Unreasonable number of lines (%d) computed from content of p
- Skipping paragraph titled '<title>' because it has an invali
- Line length cannot be negative
- Unreasonable lineLength of %d provided
AI-assisted analysis of spring-projects/spring-ai@98a7beda4f (2026-09-11).
Data as JSON: /api/errors/66130d954a8cb4e2.
Report an issue: GitHub.