{"record":{"id":"8975382ec6f4b5d3","repo":"opendataloader-project/opendataloader-pdf","slug":"page-image-fetcher-returned-null-for-page-s-897538","errorCode":null,"errorMessage":"Page image fetcher returned null for page %s","messagePattern":"Page image fetcher returned null for page (.+?)","errorType":"exception","errorClass":"IOException","httpStatus":null,"severity":"error","filePath":"java/opendataloader-pdf-core/src/main/java/org/opendataloader/pdf/hybrid/MemoryPageImageCache.java","lineNumber":37,"sourceCode":"import java.io.IOException;\nimport java.util.HashMap;\nimport java.util.Map;\n\n/**\n * In-memory page image cache. Stores images in a HashMap; evict() removes\n * entries so GC can reclaim memory (~25MB per page image).\n */\npublic class MemoryPageImageCache implements PageImageCache {\n\n    private final Map<Integer, BufferedImage> cache = new HashMap<>();\n\n    @Override\n    public BufferedImage getOrFetch(int pageIndex, PageImageFetcher fetcher) throws IOException {\n        BufferedImage image = cache.get(pageIndex);\n        if (image == null) {\n            image = fetcher.fetch(pageIndex);\n            if (image == null) {\n                throw new IOException(\"Page image fetcher returned null for page \" + pageIndex);\n            }\n            cache.put(pageIndex, image);\n        }\n        return image;\n    }\n\n    @Override\n    public void evict(int pageIndex) {\n        cache.remove(pageIndex);\n    }\n\n    @Override\n    public void close() {\n        cache.clear();\n    }\n}\n","sourceCodeStart":19,"sourceCodeEnd":54,"githubUrl":"https://github.com/opendataloader-project/opendataloader-pdf/blob/a7789b8e77dd05e2b8659eb3ea12fc458f80bfb8/java/opendataloader-pdf-core/src/main/java/org/opendataloader/pdf/hybrid/MemoryPageImageCache.java#L19-L54","documentation":"MemoryPageImageCache.getOrFetch() delegates to a PageImageFetcher callback when a page image is not in the cache. If the fetcher returns null (rather than throwing an exception), the cache treats it as a programming error — a null BufferedImage cannot be cached or processed downstream. This guard prevents a silent NPE later in the pipeline when code tries to use the image.","triggerScenarios":"Calling cache.getOrFetch(pageIndex, fetcher) where the fetcher implementation's fetch() method returns null instead of throwing an IOException on failure. This typically happens when a fetcher wraps a rendering call that returns null on error (e.g., PDFBox renderImageWithDPI returning null in some edge cases) without translating the null into an exception.","commonSituations":"Custom PageImageFetcher implementation that silently swallows rendering errors and returns null; PDFBox rendering a page with an unsupported color space or corrupt content stream where the renderer returns null rather than throwing; a fetcher that delegates to another cache layer which has been evicted/closed and returns null; a fetcher that hits an error path but forgets to throw.","solutions":["Fix the PageImageFetcher implementation to throw IOException on failure instead of returning null.","Wrap the fetcher: `cache.getOrFetch(pageIndex, idx -> { BufferedImage img = rawFetcher.fetch(idx); if (img == null) throw new IOException(\"Render returned null for page \" + idx); return img; })`.","Check whether the page is renderable before calling getOrFetch (e.g., verify the page has content and a valid media box).","Log the pageIndex to identify which page triggers the null — often a specific corrupt page."],"exampleFix":"// before: fetcher returns null on render failure, cache throws opaque error\nBufferedImage img = cache.getOrFetch(pageIndex, idx -> {\n    return renderer.renderImage(idx); // returns null on error\n});\n\n// after: fetcher throws IOException on null, cache stays happy\nBufferedImage img = cache.getOrFetch(pageIndex, idx -> {\n    BufferedImage rendered = renderer.renderImage(idx);\n    if (rendered == null) {\n        throw new IOException(\"Renderer returned null for page \" + idx);\n    }\n    return rendered;\n});","handlingStrategy":"validation","validationCode":"// Wrap the fetcher to guarantee it never returns null\nPageImageFetcher safeFetcher = pageIndex -> {\n    BufferedImage img = rawFetcher.fetch(pageIndex);\n    if (img == null) {\n        throw new IOException(\"Render returned null for page \" + pageIndex);\n    }\n    return img;\n};\nBufferedImage image = cache.getOrFetch(pageIndex, safeFetcher);","typeGuard":"public static boolean isNonNullFetcher(PageImageFetcher fetcher) {\n    return fetcher != null;\n    // Cannot statically guarantee fetch() returns non-null — must wrap at runtime.\n}","tryCatchPattern":"try {\n    image = cache.getOrFetch(pageIndex, safeFetcher);\n} catch (IOException e) {\n    if (e.getMessage().contains(\"returned null\")) {\n        // Fetcher implementation bug — fix the fetcher, don't retry with same fetcher\n        LOGGER.severe(\"PageImageFetcher returned null for page \" + pageIndex\n            + \". Fix the fetcher implementation to throw IOException on failure.\");\n        throw e;\n    }\n    throw e;\n}","preventionTips":["Always wrap PageImageFetcher implementations with a null-check guard that throws IOException.","Never let a fetcher silently return null — translate null into an exception at the fetch boundary.","Test fetcher implementations against edge-case pages (blank, corrupt, zero-size) to ensure they throw rather than return null.","Log the pageIndex when this error occurs to identify problematic pages."],"tags":["hybrid","image-cache","callback","null-safety","programming-error"],"backgroundTag":null,"analyzedSha":"a7789b8e77dd05e2b8659eb3ea12fc458f80bfb8","analyzedAt":"2026-08-14T05:22:03.953Z","schemaVersion":2},"datasetVersion":"2026-08-14T10:17:34.591Z"}