This is an automated email from the ASF dual-hosted git repository.
tballison pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/tika.git
The following commit(s) were added to refs/heads/main by this push:
new fdef6a4435 TIKA-4911: PDF page OCR: an engine's TikaException no
longer aborts the document (#3203)
fdef6a4435 is described below
commit fdef6a44359bdb250a743a3e4ca83985dea1e798
Author: Tim Allison <[email protected]>
AuthorDate: Fri Sep 18 16:04:57 2026 -0400
TIKA-4911: PDF page OCR: an engine's TikaException no longer aborts the
document (#3203)
---
CHANGES.txt | 2 +
.../apache/tika/parser/pdf/AbstractPDF2XHTML.java | 16 ++++++++
.../org/apache/tika/parser/pdf/PDFParserTest.java | 47 ++++++++++++++++++++++
3 files changed, 65 insertions(+)
diff --git a/CHANGES.txt b/CHANGES.txt
index 8c5b58bb7d..7ed0b8a415 100644
--- a/CHANGES.txt
+++ b/CHANGES.txt
@@ -1,5 +1,7 @@
Release 4.1.0 - unreleased
+ * Handle inference exceptions more robustly in PDFParser (TIKA-4911).
+
* Add ffmpeg to tika-server's "full" Docker image (TIKA-4910).
* Enable environment variable interpolation in tika-config.json (TIKA-4909).
diff --git
a/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-pdf-module/src/main/java/org/apache/tika/parser/pdf/AbstractPDF2XHTML.java
b/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-pdf-module/src/main/java/org/apache/tika/parser/pdf/AbstractPDF2XHTML.java
index bb3e9db5a4..7f1018c8a2 100644
---
a/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-pdf-module/src/main/java/org/apache/tika/parser/pdf/AbstractPDF2XHTML.java
+++
b/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-pdf-module/src/main/java/org/apache/tika/parser/pdf/AbstractPDF2XHTML.java
@@ -677,6 +677,20 @@ class AbstractPDF2XHTML extends PDFTextStripper {
}
}
+ /**
+ * An engine's own failure on one page (a remote OCR service answering 401
or 500, a
+ * tesseract exit code): recorded like a timeout, the page keeps its text,
the remaining
+ * pages still run, and the first failure surfaces once every page has
been attempted.
+ */
+ void handleCatchableEngineFailure(TikaException e) throws TikaException {
+ if (config.isCatchIntermediateIOExceptions()) {
+ EmbeddedDocumentUtil.recordException(e, metadata, context);
+ exceptions.add(e);
+ } else {
+ throw e;
+ }
+ }
+
/** What the OCR step did with a page. */
enum PageOcr {
/** No engine was dispatched: NO_OCR, no engine, or maxPagesToOcr
spent. */
@@ -764,6 +778,8 @@ class AbstractPDF2XHTML extends PDFTextStripper {
handleCatchableIOE(e);
} catch (TikaTimeoutException e) {
handleCatchableTimeout(e);
+ } catch (TikaException e) {
+ handleCatchableEngineFailure(e);
} catch (SAXException e) {
throw new IOException("error writing OCR content from PDF", e);
}
diff --git
a/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-pdf-module/src/test/java/org/apache/tika/parser/pdf/PDFParserTest.java
b/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-pdf-module/src/test/java/org/apache/tika/parser/pdf/PDFParserTest.java
index 949fcc7d9f..e48e93a0ae 100644
---
a/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-pdf-module/src/test/java/org/apache/tika/parser/pdf/PDFParserTest.java
+++
b/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-pdf-module/src/test/java/org/apache/tika/parser/pdf/PDFParserTest.java
@@ -1728,6 +1728,37 @@ public class PDFParserTest extends TikaTest {
assertWellFormed(xml);
}
+ /**
+ * An engine's TikaException (a remote OCR service answering 401) is
handled like its
+ * IOException: recorded, the page keeps its text, later pages parse, the
failure surfaces
+ * at the end. Before, it escaped as "Unable to end a page" and lost every
page's text.
+ */
+ @Test
+ public void testAutoOcrEngineRefusalFallsBackToText() throws Exception {
+ PDFParserConfig config = autoOcrTriggeringPage16();
+ ParseContext context = new ParseContext();
+ context.set(PDFParserConfig.class, config);
+ context.set(Parser.class, refusingOcrParser(config));
+ Metadata metadata = new Metadata();
+ ToXMLContentHandler handler = new ToXMLContentHandler();
+ TikaException thrown = null;
+ try (TikaInputStream tis =
+
getResourceAsStream("/test-documents/testPDF_bad_page_303226.pdf")) {
+ new PDFParser().parse(tis, handler, metadata, context);
+ } catch (TikaException e) {
+ thrown = e;
+ }
+ String xml = handler.toString();
+
+ assertContains("42936", xml);
+ assertContains("ANABOLIC", xml);
+ assertContains("1308.44", xml);
+ assertContains("HTTP 401", String.join("\n",
+
metadata.getValues(TikaCoreProperties.TIKA_META_EXCEPTION_WARNING)));
+ assertNotNull(thrown, "the recorded refusal still surfaces once every
page is done");
+ assertWellFormed(xml);
+ }
+
/** TIKA-4883: once maxPagesToOcr is exhausted, later triggering pages
keep their text. */
@Test
public void testAutoOcrMaxPagesFallsBackToText() throws Exception {
@@ -2269,6 +2300,22 @@ public class PDFParserTest extends TikaTest {
};
}
+ /** A hosted engine refusing the request: the VLM parsers wrap HTTP errors
this way. */
+ private static Parser refusingOcrParser(PDFParserConfig config) {
+ return new MockEngine() {
+ @Override
+ public Set<MediaType> getSupportedTypes(ParseContext context) {
+ return Collections.singleton(ocrMediaType(config));
+ }
+
+ @Override
+ public void parse(TikaInputStream tis, ContentHandler handler,
Metadata metadata,
+ ParseContext context) throws TikaException {
+ throw new TikaException("HTTP 401 from
https://ocr.example/v1/chat/completions");
+ }
+ };
+ }
+
private static Parser failingOcrParser(PDFParserConfig config) {
return new MockEngine() {
@Override