opendataloader-project/opendataloader-pdf · error · IOException
pdf2img PAGE_PNG_DATA is not a readable image
Error message
pdf2img PAGE_PNG_DATA is not a readable image
What it means
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.
Source
Thrown at java/opendataloader-pdf-core/src/main/java/org/opendataloader/pdf/hybrid/HancomAIClient.java:726
String pngBase64 = innerResult.has("PAGE_PNG_DATA")
? innerResult.get("PAGE_PNG_DATA").asText() : null;
if (pngBase64 == null || pngBase64.isEmpty()) {
throw new IOException("pdf2img PAGE_PNG_DATA is empty");
}
byte[] pngBytes;
try {
pngBytes = Base64.getDecoder().decode(pngBase64);
} catch (IllegalArgumentException e) {
// fetchPageImage is declared to throw IOException and callers catch
// only IOException. Escaping IAE would abort the whole conversion
// instead of skipping the failed page.
throw new IOException("pdf2img PAGE_PNG_DATA is not valid Base64", e);
}
BufferedImage image = ImageIO.read(new ByteArrayInputStream(pngBytes));
if (image == null) {
throw new IOException("pdf2img PAGE_PNG_DATA is not a readable image");
}
if (cropOutput.active()) {
savePageImageFile(cropOutput.directory(), pageIndex, pngBytes);
}
return image;
}
}
/**
* Sends a cropped image to IMAGE_CAPTIONING and returns the caption text.
*/
/** Image-captioning result: caption text + the model's self-reported confidence. */
static final class CaptionResult {
final String caption;
final Double confidence;
CaptionResult(String caption, Double confidence) {
this.caption = caption;
this.confidence = confidence;View on GitHub (pinned to a7789b8e77)
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.
Example fix
// before: no retry, single fetch fails the page
BufferedImage img = cache.getOrFetch(pageIndex, HancomAIClient.this::fetchPageImage);
// after: retry once before giving up on the page
BufferedImage img = null;
for (int attempt = 0; attempt < 2 && img == null; attempt++) {
try {
img = cache.getOrFetch(pageIndex, HancomAIClient.this::fetchPageImage);
} catch (IOException e) {
if (attempt == 1) throw e;
LOGGER.warning("Retrying page " + pageIndex + " after image decode failure");
}
} Defensive patterns
Strategy: retry
Validate before calling
// Before calling fetchPageImage, validate the server health client.checkAvailability(); // throws IOException if server unreachable // Cannot pre-validate image decodability — the bytes come from the server at runtime.
Try / catch
try {
BufferedImage image = cache.getOrFetch(pageIndex, this::fetchPageImage);
} catch (IOException e) {
if (e.getMessage().contains("not a readable image")) {
// Retry once — often transient (truncated response)
image = fetchPageImage(pageIndex);
} else {
throw e;
}
} Prevention
- 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.
When it happens
Trigger: 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.
Common situations: 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).
Related errors
- Hancom AI server at %s returned HTTP %s
- Hancom AI server is not available at %s Check that the serve
- pdf2img returned HTTP %s
- pdf2img PAGE_PNG_DATA is not valid Base64
- Option --%s: unsupported value '%s'. Supported values: %s, %
AI-assisted analysis of opendataloader-project/opendataloader-pdf@a7789b8e77 (2026-08-14).
Data as JSON: /api/errors/e5918226dc4f346d.
Report an issue: GitHub.