{"record":{"id":"92478eee039972d9","repo":"opendataloader-project/opendataloader-pdf","slug":"hancom-visualinfo-failed-with-status-s-s","errorCode":null,"errorMessage":"Hancom visualinfo failed with status %s: %s","messagePattern":"Hancom visualinfo failed with status (.+?): (.+?)","errorType":"exception","errorClass":"IOException","httpStatus":null,"severity":"error","filePath":"java/opendataloader-pdf-core/src/main/java/org/opendataloader/pdf/hybrid/HancomClient.java","lineNumber":238,"sourceCode":"     * @return The visual info JSON response.\n     * @throws IOException If the request fails.\n     */\n    private JsonNode getVisualInfo(String fileId) throws IOException {\n        String url = baseUrl + String.format(VISUALINFO_ENDPOINT, fileId) +\n            \"?engine=\" + ENGINE +\n            \"&dlaMode=\" + DLA_MODE +\n            \"&ocrMode=\" + OCR_MODE;\n\n        Request request = new Request.Builder()\n            .url(url)\n            .get()\n            .build();\n\n        try (Response response = httpClient.newCall(request).execute()) {\n            if (!response.isSuccessful()) {\n                ResponseBody body = response.body();\n                String bodyStr = body != null ? body.string() : \"\";\n                throw new IOException(\"Hancom visualinfo failed with status \" + response.code() + \": \" + bodyStr);\n            }\n\n            ResponseBody body = response.body();\n            if (body == null) {\n                throw new IOException(\"Empty response body from visualinfo\");\n            }\n\n            return objectMapper.readTree(body.string());\n        }\n    }\n\n    /**\n     * Deletes an uploaded file from the server.\n     *\n     * <p>This method silently ignores any errors to ensure cleanup\n     * doesn't interfere with the main processing result.\n     *\n     * @param fileId The file ID to delete.","sourceCodeStart":220,"sourceCodeEnd":256,"githubUrl":"https://github.com/opendataloader-project/opendataloader-pdf/blob/a7789b8e77dd05e2b8659eb3ea12fc458f80bfb8/java/opendataloader-pdf-core/src/main/java/org/opendataloader/pdf/hybrid/HancomClient.java#L220-L256","documentation":"The Hancom visualinfo endpoint (GET with fileId, dlaMode, and ocrMode parameters) returned an HTTP status outside the 2xx range. The error includes the status code and raw response body. This call happens after a successful upload, so it indicates the server accepted the file but cannot or will not process it for visual info extraction.","triggerScenarios":"Calling HancomClient.getVisualInfo(fileId) after uploadFile() succeeded. Specific triggers: 404 (fileId not found — file was already deleted or expired), 408/504 (server-side processing timeout for a very large or complex PDF), 422 (server cannot parse the PDF's visual structure), 500/502/503 (internal error during visual info extraction), 401/403 (auth token expired between upload and visualinfo calls).","commonSituations":"Large multi-page PDFs that take too long for the server to process within its internal timeout; the fileId expired between upload and visualinfo due to server-side TTL; server restart cleared uploaded files from its in-memory store; the uploaded PDF contains elements the visualinfo engine cannot handle (corrupt XObjects, encrypted streams, exotic color spaces).","solutions":["Inspect the response body in the error message for the server's failure reason code.","For 404: the fileId may have expired — re-upload the PDF and immediately call getVisualInfo.","For 408/504: the PDF may be too large/complex — try splitting it or reducing page count.","For 5xx: retry with backoff — transient server-side processing failures are common under load.","Verify the uploaded file is a valid, unencrypted PDF using `qpdf --check doc.pdf` or `java -jar pdfbox-app.jar PDFParser doc.pdf`.","Check that DLA_MODE and OCR_MODE constants match the server's expected parameter values."],"exampleFix":"// before: no retry, transient 5xx failures abort the conversion\nJsonNode visualInfo = client.getVisualInfo(fileId);\n\n// after: retry visualinfo with backoff for transient server errors\nJsonNode visualInfo = null;\nfor (int attempt = 0; attempt < 3; attempt++) {\n    try {\n        visualInfo = client.getVisualInfo(fileId);\n        break;\n    } catch (IOException e) {\n        if (!e.getMessage().contains(\"status 5\") || attempt == 2) throw e;\n        Thread.sleep((1L << attempt) * 1000);\n    }\n}","handlingStrategy":"retry","validationCode":"// Validate fileId format before calling getVisualInfo\nif (fileId == null || fileId.isEmpty()) {\n    throw new IllegalStateException(\"fileId is null or empty — upload may have failed silently\");\n}\n// Cannot pre-validate that the server will accept the fileId.","typeGuard":null,"tryCatchPattern":"try {\n    JsonNode visualInfo = client.getVisualInfo(fileId);\n} catch (IOException e) {\n    String msg = e.getMessage();\n    if (msg.contains(\"status 404\")) {\n        // fileId expired — re-upload and retry\n        fileId = client.uploadFile(pdfBytes);\n        visualInfo = client.getVisualInfo(fileId);\n    } else if (msg.contains(\"status 5\") || msg.contains(\"status 429\")) {\n        // Transient — retry with backoff\n        Thread.sleep(2000);\n        visualInfo = client.getVisualInfo(fileId);\n    } else {\n        throw e;\n    }\n}","preventionTips":["Call getVisualInfo immediately after uploadFile to minimize fileId expiry risk.","Implement retry with backoff for 5xx and 429 responses.","For 404, re-upload the PDF rather than retrying with the same fileId.","Set a reasonable timeout to avoid hanging on slow server-side processing."],"tags":["network","http","hancom","visualinfo","hybrid"],"backgroundTag":null,"analyzedSha":"a7789b8e77dd05e2b8659eb3ea12fc458f80bfb8","analyzedAt":"2026-08-14T05:22:03.953Z","schemaVersion":2},"datasetVersion":"2026-08-14T10:17:34.591Z"}