opendataloader-project/opendataloader-pdf · error · IOException

Page image fetcher returned null for page %s

Error message

Page image fetcher returned null for page %s

What it means

DiskPageImageCache.getOrFetch requires the supplied PageImageFetcher.fetch(pageIndex) to return a non-null BufferedImage so it can write the PNG to disk and hand it back. A null return means no image was produced for that page and the cache has nothing to persist or return, so it fails fast with IOException rather than writing an empty file or propagating a null downstream.

Source

Thrown at java/opendataloader-pdf-core/src/main/java/org/opendataloader/pdf/hybrid/DiskPageImageCache.java:61

    DiskPageImageCache(Path tempDir) {
        this.tempDir = tempDir;
    }

    @Override
    public BufferedImage getOrFetch(int pageIndex, PageImageFetcher fetcher) throws IOException {
        Path file = tempDir.resolve("page-" + pageIndex + ".png");
        if (Files.exists(file)) {
            BufferedImage cached = ImageIO.read(file.toFile());
            if (cached != null) {
                return cached;
            }
            // Cached file is unreadable (corrupt or no ImageReader) — re-fetch.
            LOGGER.log(Level.WARNING, "Cached page image is unreadable, re-fetching: {0}", file);
            Files.deleteIfExists(file);
        }
        BufferedImage image = fetcher.fetch(pageIndex);
        if (image == null) {
            throw new IOException("Page image fetcher returned null for page " + pageIndex);
        }
        if (!ImageIO.write(image, "png", file.toFile())) {
            throw new IOException("No ImageIO writer accepted PNG output for page " + pageIndex);
        }
        return image;
    }

    @Override
    public void evict(int pageIndex) {
        // no-op: keep on disk for potential re-read
    }

    @Override
    public void close() throws IOException {
        if (!Files.exists(tempDir)) {
            return;
        }
        try (DirectoryStream<Path> stream = Files.newDirectoryStream(tempDir)) {

View on GitHub (pinned to a7789b8e77)

Solutions

  1. Make the PageImageFetcher return a non-null BufferedImage for every requested page index
  2. Throw IOException from the fetcher on failure instead of returning null
  3. Validate the page-index range against the document before calling getOrFetch

Example fix

// before
BufferedImage fetch(int i) {
    if (outOfRange(i)) return null;
    ...
}
// after
BufferedImage fetch(int i) throws IOException {
    if (outOfRange(i)) throw new IOException("page " + i + " out of range");
    ...
}
Defensive patterns

Strategy: try-catch

Try / catch

// For a custom PageImageFetcher, validate the contract yourself
PageImageFetcher safeFetcher = idx -> {
    BufferedImage img = realFetcher.fetch(idx);
    if (img == null) throw new IOException("no image for page " + idx);
    return img;
};
BufferedImage out = cache.getOrFetch(pageIndex, safeFetcher);

Prevention

When it happens

Trigger: A PageImageFetcher lambda passed to getOrFetch returns null for a page index. In the shipped pipeline the fetcher is HancomAIClient.fetchPageImage, which throws IOException instead of returning null, so this only fires for a custom/modified fetcher or a future code path that adds a null return.

Common situations: Writing a custom PageImageCache or fetcher for tests/stubs; a third-party image source that returns null on unsupported page indices; an OCR/render hook that swallows an error and yields null.

Related errors


AI-assisted analysis of opendataloader-project/opendataloader-pdf@a7789b8e77 (2026-08-14). Data as JSON: /api/errors/66fc07098ca5875b. Report an issue: GitHub.