{"record":{"id":"3e1bf833221aa287","repo":"opendataloader-project/opendataloader-pdf","slug":"is-not-a-valid-pdf-file-missing-pdf-header","errorCode":null,"errorMessage":"'{}' is not a valid PDF file (missing %PDF- header).","messagePattern":"'(.+?)' is not a valid PDF file \\(missing %PDF- header\\)\\.","errorType":"validation","errorClass":"InvalidPdfFileException","httpStatus":null,"severity":"error","filePath":"java/opendataloader-pdf-core/src/main/java/org/opendataloader/pdf/processors/DocumentProcessor.java","lineNumber":694,"sourceCode":"     *\n     * <p>ISO 32000-1 §7.5.2 allows the {@code %PDF-} header to appear \"near\n     * the beginning\" of the file rather than strictly at byte 0; real-world\n     * PDFs sometimes have a leading UTF-8 BOM or whitespace. A 1024-byte\n     * search window matches that tolerance while still rejecting any\n     * JPG/PNG/HTML/empty file.\n     *\n     * @throws InvalidPdfFileException if the magic number is not present\n     * @throws IOException if the file cannot be opened or read\n     */\n    private static void validatePdfMagicNumber(String pdfName) throws IOException {\n        Path path = Path.of(pdfName);\n        byte[] head;\n        try (InputStream in = Files.newInputStream(path)) {\n            head = in.readNBytes(1024);\n        }\n        byte[] marker = \"%PDF-\".getBytes(StandardCharsets.US_ASCII);\n        if (indexOfBytes(head, marker) < 0) {\n            throw new InvalidPdfFileException(\n                \"'\" + displayName(pdfName) + \"' is not a valid PDF file (missing %PDF- header).\");\n        }\n    }\n\n    /**\n     * Verifies the JVM temporary directory is writable.\n     *\n     * <p>veraPDF streams anything larger than a small in-memory threshold\n     * through a temporary file: the Standard 14 font metrics embedded in the\n     * jar, embedded font programs, CMaps and decoded content streams all take\n     * that path. A PDF stream has no size bound while memory does, so writing\n     * to disk is by design and cannot be avoided by buffering.\n     *\n     * <p>When the temporary directory is not writable those reads fail deep\n     * inside veraPDF, where the failure is logged at {@code FINE} and\n     * swallowed. Processing then continues on missing data and surfaces as an\n     * unrelated {@code NullPointerException}, or — worse — completes with\n     * exit code 0 while silently dropping most of the text. Failing up front","sourceCodeStart":676,"sourceCodeEnd":712,"githubUrl":"https://github.com/opendataloader-project/opendataloader-pdf/blob/a7789b8e77dd05e2b8659eb3ea12fc458f80bfb8/java/opendataloader-pdf-core/src/main/java/org/opendataloader/pdf/processors/DocumentProcessor.java#L676-L712","documentation":"InvalidPdfFileException thrown by validatePdfMagicNumber before veraPDF is ever invoked: the first 1024 bytes of the file do not contain the ASCII %PDF- marker. This is the 'not a PDF at all' failure mode, distinct from error 81 (header present, body broken). Detected cheaply so the cost of a full veraPDF parse is avoided on obviously wrong input.","triggerScenarios":"Any public entry point (DocumentProcessor.processFile/extractContents/preprocessing/processFileWithResult, OpenDataLoaderPDF.processFile, AutoTagger.tag) is called with a path whose content lacks %PDF- in the first 1024 bytes — e.g. a JPEG/PNG/HTML/ZIP/text file, an empty file, or a PostScript file.","commonSituations":"A user renamed a .jpg/.png/.html to .pdf. The wrong file was passed (a .txt report instead of the PDF). An empty placeholder file. A path pointing at an OpenDocument or Office file exported to the wrong format.","solutions":["Confirm the file is actually a PDF: check the first bytes (head -c 5 file.pdf should print %PDF-) or run file(1).","Re-export or re-save the source document as PDF from the originating application.","If accepting user uploads, validate the magic number on your side before calling processFile and reject early with your own message.","Check the path is not pointing at a directory or a symlink to a non-PDF target."],"exampleFix":"# before: file is a renamed image\n$ mv scan.jpg report.pdf && odl-pdf report.pdf\n-> InvalidPdfFileException: 'report.pdf' is not a valid PDF file (missing %PDF- header).\n# after: export to real PDF\n$ file report.pdf   # -> PDF document, version 1.4\n$ odl-pdf report.pdf","handlingStrategy":"validation","validationCode":"// Validate the magic number yourself before calling the library:\nprivate static boolean looksLikePdf(Path p) throws IOException {\n    try (InputStream in = Files.newInputStream(p)) {\n        byte[] head = in.readNBytes(5);\n        byte[] marker = \"%PDF-\".getBytes(StandardCharsets.US_ASCII);\n        return Arrays.equals(head, marker);\n    }\n}","typeGuard":"static boolean isMissingHeader(IOException e) {\n    return e instanceof InvalidPdfFileException\n        && e.getMessage() != null\n        && e.getMessage().contains(\"missing %PDF- header\");\n}","tryCatchPattern":"try {\n    DocumentProcessor.processFile(pdfName, config);\n} catch (InvalidPdfFileException e) {\n    if (e.getMessage() != null && e.getMessage().contains(\"missing %PDF- header\")) {\n        rejectUpload(\"File is not a PDF (no %PDF- header).\");\n    } else {\n        throw e; // corrupted-content variant (error 81)\n    }\n}","preventionTips":["Check the first 5 bytes for %PDF- on user uploads before queuing for conversion.","Use the `file` command or a mime-type check in your ingest layer.","Reject non-PDF files with your own message rather than letting the library surface it mid-pipeline."],"tags":["pdf-validation","magic-number","corrupt-input","invalidpdffileexception"],"backgroundTag":null,"analyzedSha":"a7789b8e77dd05e2b8659eb3ea12fc458f80bfb8","analyzedAt":"2026-08-14T05:22:03.953Z","schemaVersion":2},"datasetVersion":"2026-08-14T10:17:34.591Z"}