{"record":{"id":"e5918226dc4f346d","repo":"opendataloader-project/opendataloader-pdf","slug":"pdf2img-page-png-data-is-not-a-readable-image","errorCode":null,"errorMessage":"pdf2img PAGE_PNG_DATA is not a readable image","messagePattern":"pdf2img PAGE_PNG_DATA is not a readable image","errorType":"exception","errorClass":"IOException","httpStatus":null,"severity":"error","filePath":"java/opendataloader-pdf-core/src/main/java/org/opendataloader/pdf/hybrid/HancomAIClient.java","lineNumber":726,"sourceCode":"\n            String pngBase64 = innerResult.has(\"PAGE_PNG_DATA\")\n                ? innerResult.get(\"PAGE_PNG_DATA\").asText() : null;\n            if (pngBase64 == null || pngBase64.isEmpty()) {\n                throw new IOException(\"pdf2img PAGE_PNG_DATA is empty\");\n            }\n\n            byte[] pngBytes;\n            try {\n                pngBytes = Base64.getDecoder().decode(pngBase64);\n            } catch (IllegalArgumentException e) {\n                // fetchPageImage is declared to throw IOException and callers catch\n                // only IOException. Escaping IAE would abort the whole conversion\n                // instead of skipping the failed page.\n                throw new IOException(\"pdf2img PAGE_PNG_DATA is not valid Base64\", e);\n            }\n            BufferedImage image = ImageIO.read(new ByteArrayInputStream(pngBytes));\n            if (image == null) {\n                throw new IOException(\"pdf2img PAGE_PNG_DATA is not a readable image\");\n            }\n            if (cropOutput.active()) {\n                savePageImageFile(cropOutput.directory(), pageIndex, pngBytes);\n            }\n            return image;\n        }\n    }\n\n    /**\n     * Sends a cropped image to IMAGE_CAPTIONING and returns the caption text.\n     */\n    /** Image-captioning result: caption text + the model's self-reported confidence. */\n    static final class CaptionResult {\n        final String caption;\n        final Double confidence;\n        CaptionResult(String caption, Double confidence) {\n            this.caption = caption;\n            this.confidence = confidence;","sourceCodeStart":708,"sourceCodeEnd":744,"githubUrl":"https://github.com/opendataloader-project/opendataloader-pdf/blob/a7789b8e77dd05e2b8659eb3ea12fc458f80bfb8/java/opendataloader-pdf-core/src/main/java/org/opendataloader/pdf/hybrid/HancomAIClient.java#L708-L744","documentation":"The Hancom AI backend returned a PAGE_PNG_DATA field that is valid Base64 but whose decoded bytes are not an image format that javax.imageio.ImageIO can decode. ImageIO.read() returns null when it finds no registered ImageReader for the byte stream's magic bytes. This is a page-level failure inside fetchPageImage, which is declared to throw only IOException so the per-page pipeline can skip the failed page rather than aborting the whole document.","triggerScenarios":"Called when HancomAIClient.fetchPageImage() processes a pdf2img response where RESULT[0].RESULT.PAGE_PNG_DATA is non-empty Base64 but decodes to corrupt/truncated/wrong-format bytes (e.g., a JPEG masquerading in a field named PNG_DATA, a truncated response from a network glitch, or a server-side rendering failure that wrote an error string instead of image data). The Base64.decode succeeds but ImageIO.read(new ByteArrayInputStream(pngBytes)) returns null.","commonSituations":"Hancom AI server under heavy load producing truncated image payloads; a proxy or CDN truncating the Base64 string at a chunk boundary; server-side pdf2img module crashing mid-render and returning a partial or zero-length binary; content-type mismatch where the backend embeds a format ImageIO has no reader for (e.g., AVIF, WebP without native plugins).","solutions":["Retry the page: this is often transient (truncated response), so calling fetchPageImage again for the same pageIndex may succeed on a clean response.","Verify the Hancom AI server's pdf2img endpoint is healthy by calling it directly with a known-good PDF and inspecting the PAGE_PNG_DATA output.","If using a reverse proxy or load balancer in front of the Hancom API, check for response size limits or body truncation settings.","Register additional ImageIO readers (e.g., add a WebP or TIFF plugin JAR to the classpath) if the backend may return formats beyond PNG/JPEG.","Enable cropOutput (--save-crops) to dump the raw pngBytes to disk for offline inspection of what the server actually returned."],"exampleFix":"// before: no retry, single fetch fails the page\nBufferedImage img = cache.getOrFetch(pageIndex, HancomAIClient.this::fetchPageImage);\n\n// after: retry once before giving up on the page\nBufferedImage img = null;\nfor (int attempt = 0; attempt < 2 && img == null; attempt++) {\n    try {\n        img = cache.getOrFetch(pageIndex, HancomAIClient.this::fetchPageImage);\n    } catch (IOException e) {\n        if (attempt == 1) throw e;\n        LOGGER.warning(\"Retrying page \" + pageIndex + \" after image decode failure\");\n    }\n}","handlingStrategy":"retry","validationCode":"// Before calling fetchPageImage, validate the server health\nclient.checkAvailability(); // throws IOException if server unreachable\n// Cannot pre-validate image decodability — the bytes come from the server at runtime.","typeGuard":null,"tryCatchPattern":"try {\n    BufferedImage image = cache.getOrFetch(pageIndex, this::fetchPageImage);\n} catch (IOException e) {\n    if (e.getMessage().contains(\"not a readable image\")) {\n        // Retry once — often transient (truncated response)\n        image = fetchPageImage(pageIndex);\n    } else {\n        throw e;\n    }\n}","preventionTips":["Monitor the Hancom AI server's pdf2img endpoint health and alert on elevated error rates.","Use a disk-based image cache (imageCache='disk') to persist successfully rendered pages across retries.","Add an OkHttp logging interceptor to capture raw response sizes for diagnosis."],"tags":["network","image-processing","hancom-ai","hybrid","base64"],"backgroundTag":null,"analyzedSha":"a7789b8e77dd05e2b8659eb3ea12fc458f80bfb8","analyzedAt":"2026-08-14T05:22:03.953Z","schemaVersion":2},"datasetVersion":"2026-08-14T10:17:34.591Z"}