{"record":{"id":"01347dbabd5d3d60","repo":"spring-projects/spring-ai","slug":"skipping-paragraph-titled-title-because-it-has","errorCode":null,"errorMessage":"Skipping paragraph titled '<title>' because it has an invalid start page number: <pageNumber>","messagePattern":"Skipping paragraph titled '<title>' because it has an invalid start page number: <pageNumber>","errorType":"console","errorClass":null,"httpStatus":null,"severity":"warning","filePath":"document-readers/spring-ai-pdf-document-reader/src/main/java/org/springframework/ai/reader/pdf/ParagraphPdfDocumentReader.java","lineNumber":181,"sourceCode":"\n\t\treturn document;\n\t}\n\n\tprotected void addMetadata(Paragraph from, Paragraph to, Document document) {\n\t\tdocument.getMetadata().put(METADATA_TITLE, from.title());\n\t\tdocument.getMetadata().put(METADATA_START_PAGE, from.startPageNumber());\n\t\tdocument.getMetadata().put(METADATA_END_PAGE, from.endPageNumber());\n\t\tdocument.getMetadata().put(METADATA_LEVEL, from.level());\n\t\tif (this.resourceFileName != null) {\n\t\t\tdocument.getMetadata().put(METADATA_FILE_NAME, this.resourceFileName);\n\t\t}\n\t}\n\n\tpublic String getTextBetweenParagraphs(Paragraph fromParagraph, Paragraph toParagraph) {\n\n\t\tif (fromParagraph.startPageNumber() < 1) {\n\t\t\tif (logger.isWarnEnabled()) {\n\t\t\t\tlogger.warn(\"Skipping paragraph titled '\" + fromParagraph.title()\n\t\t\t\t\t\t+ \"' because it has an invalid start page number: \" + fromParagraph.startPageNumber());\n\t\t\t}\n\t\t\treturn \"\";\n\t\t}\n\n\t\t// Page started from index 0, while PDFBOx getPage return them from index 1.\n\t\tint startPage = fromParagraph.startPageNumber() - 1;\n\t\tint endPage = toParagraph.startPageNumber() - 1;\n\n\t\tif (fromParagraph == toParagraph || endPage < startPage) {\n\t\t\tendPage = startPage;\n\t\t}\n\n\t\ttry {\n\n\t\t\tStringBuilder sb = new StringBuilder();\n\n\t\t\tvar pdfTextStripper = new PDFLayoutTextStripperByArea();","sourceCodeStart":163,"sourceCodeEnd":199,"githubUrl":"https://github.com/spring-projects/spring-ai/blob/98a7beda4f29d80a71c5837eb4053b03a93a46f7/document-readers/spring-ai-pdf-document-reader/src/main/java/org/springframework/ai/reader/pdf/ParagraphPdfDocumentReader.java#L163-L199","documentation":"The PDF paragraph reader (PDFBox-based TOC/paragraph extraction) encountered a paragraph whose start page number is less than 1, i.e. invalid for a 1-indexed PDF page model. Instead of failing, it skips the paragraph, logs this warning, and returns an empty string for the text range, so that paragraph's content is silently missing from the extracted document.","triggerScenarios":"Calling getTextBetweenParagraphs (via docText) on a Paragraph whose startPageNumber() returns 0 or negative — typically a malformed or synthetic Paragraph produced by parsing an unusual PDF outline/TOC (e.g. page labels that don't resolve to real pages).","commonSituations":"Reading PDFs with non-standard or corrupted bookmarks/TOC structures; PDFs produced by generators that emit bogus page references in the document outline; relying on ParagraphPdfDocumentReader where the PDF's logical structure does not match physical pages.","solutions":["Inspect the PDF's outline/bookmarks with a PDF inspector and repair or regenerate the source PDF.","Fall back to PagePdfDocumentReader, which reads by explicit page ranges and ignores the TOC structure.","Pre-validate the extracted paragraphs and filter out ones with startPageNumber < 1 before calling getTextBetweenParagraphs.","If the paragraph content matters, extract its text via an alternate path (e.g. PDFTextStripper over the whole document) and match by title."],"exampleFix":"// before: unguarded extraction\nString text = reader.getTextBetweenParagraphs(from, to);\n// after: validate first\nif (from.startPageNumber() >= 1) {\n    String text = reader.getTextBetweenParagraphs(from, to);\n} else {\n    logger.warn(\"Skipping invalid paragraph: \" + from.title());\n}","handlingStrategy":"fallback","validationCode":"if (paragraph.startPageNumber() < 1) {\n    logger.warn(\"Invalid paragraph '\" + paragraph.title() + \"', skipping\");\n    return \"\";\n}","typeGuard":"boolean hasValidPageRange(Paragraph p) {\n    return p != null && p.startPageNumber() >= 1;\n}","tryCatchPattern":"// getTextBetweenParagraphs does not throw; check for empty result\nString text = reader.getTextBetweenParagraphs(from, to);\nif (text.isEmpty()) {\n    // fall back to whole-document extraction by page range\n    text = pageReader.get(p1, p2);\n}","preventionTips":["Validate PDF outlines before using ParagraphPdfDocumentReader.","Prefer PagePdfDocumentReader for PDFs with unreliable bookmarks.","Log which paragraph titles were skipped so missing content is noticed.","Test your reader against the actual PDF corpus you will ingest."],"tags":["pdf","document-reader","data-loss","parsing"],"backgroundTag":"value-out-of-range","analyzedSha":"98a7beda4f29d80a71c5837eb4053b03a93a46f7","analyzedAt":"2026-09-11T14:15:49.441Z","contentChangedAt":"2026-09-11T14:15:49.441Z","schemaVersion":2},"datasetVersion":"2026-09-14T11:17:12.474Z"}