OCR with Tesseract

This content was generated with the assistance of AI and should be verified against the official documentation before being relied on in production.

The reference material consulted while preparing these pages is listed in the section bibliography.

Baseline date: 2026-10-12. This page was written against Apache Tika 4.1.0 (the latest release on Maven Central on that date) with the 3.3.x differences noted, Java 21 and later for the examples (Tika 4.x needs Java 17), Spring Boot 4.x on Spring Framework 7 for the integration pages, and the Docker images apache/tika:4.1.0 and apache/tika:4.1.0-full. Tika has no OCR engine of its own. It finds pixels that may hold text (an image file, a picture embedded in an Office document or an email, a PDF page that has no usable text layer) and hands them to an external engine, by default the open-source Tesseract program. This page explains when that is worth the cost, how to install Tesseract and its language data (or take the -full image that ships it), every TesseractOCRParser option, the text-recognizers list that 4.1 introduced to choose the engine, the in-process Tess4J alternative, how OCR of a standalone image differs from OCR of a rendered PDF page, and how to keep OCR from eating the whole timeout budget.

Related pages: Configuration (the tika-config.json format and TikaLoader), PDF Parsing (the PDF parser options and the per-page decision in depth), VLM Parsers & Inference (vision-language models as an OCR engine), Extending Tika (writing your own engine), Setup & Installation (dependencies and the Docker tags) and Supported Formats & Parser Families (why a missing binary makes a parser silent).

How this page was checked: the option names, defaults and validation rules were read from the 4.1.0 and 3.3.2 sources jars of tika-parser-ocr-module, the 4.1.0 sources jar of tika-parser-tess4j-module and the 3.3.2 sources of tika-parser-pdf-module, and cross-checked against the 4.1.x documentation pages cited under each example. Tesseract was not installed on the test machine for this page and no Tika image was available to Docker, so the programs below were compiled and run against Tika 4.1.0 on Java 25 without an OCR engine: the outputs shown are the real ones for that situation (config validation, engine-selection errors, the PDF OCR verdict). No Tesseract recognition output appears on this page, and the section What was verified and what was not lists the rest.

When OCR is needed

A parser extracts text that a file stores as text. A scanner or a camera stores pictures, so a scanned contract, a photographed page or a book cover has no text for a parser to find. OCR is the step that turns those pixels back into characters. In the Bookshelf intake scenario:

File Without OCR With OCR

scanned-loan-form.pdf (a scanner output: one image per page)

Metadata only. The text body is empty and the PDF parser records pdf:chars-per-page = 0.

The loan form text (borrower, title, due date), recognised page by page.

book-cover.jpg

EXIF and image metadata from JpegParser, no text.

The title and author printed on the cover.

catalogue.pdf (generated by a word processor)

The full text from the PDF text layer.

Nothing better: OCR of a page that already has text only costs time. The default AUTO policy leaves such pages alone.

manuscript.docx or publisher-email.eml with embedded pictures

The document text; each picture becomes an embedded document with metadata only.

Text inside the pictures too (a pasted screenshot, a signature stamp, a scanned attachment).

renamed-invoice.png (really a PDF)

Detection finds application/pdf, so the PDF path applies, not the image path.

As for any PDF: OCR only on pages whose text layer is poor.

Typical reasons to turn OCR on are scanned archives, faxes, mobile-phone photos of documents, image-only PDFs produced by "print to image" tools, and screenshots in tickets or emails. Typical reasons to keep it off are a corpus of born-digital documents, a latency budget measured in milliseconds, or an index where image text is noise (logos, decorative banners).

What OCR does not give you:

  • Layout. Tesseract returns lines of text, optionally with word boxes in hOCR. Tika does not rebuild columns, reading order across complex layouts or tables from that output. For layout-aware extraction see the escalation advice in Document Parsing for RAG.

  • Certainty. Recognition quality depends on the scan resolution, skew, noise, fonts and the language model. Expect errors on low-quality input and check them (the junk-detection and text-quality signals described in Character Encoding Detection help to flag bad output).

  • Handwriting. The standard Tesseract models target printed text. Handwritten fields on the loan form are, at best, partly recognised. A vision-language model (see VLM Parsers & Inference) is the usual alternative.

The cost is real. One page at 300 dpi typically takes from a fraction of a second to several seconds of CPU in Tesseract, far more than the text extraction of a born-digital page. The source of TesseractOCRParser logs a one-time INFO line the first time it runs, reminding that Tesseract "can add greatly to processing time".

How OCR fits into Tika 4.1

How Tika 4.1 reaches an OCR engine: image-parser and pdf-parser call the engine named in text-recognizers, after the per-entry gates and the PDF page policy

The picture above is the whole design in one view:

  1. A container parser meets pixels. image-parser (the JPEG, PNG, TIFF, BMP and GIF parsers) parses an image file or an embedded image for its metadata. pdf-parser extracts each page text layer and, when its page policy says so, renders the page to an image. The EMF and WMF parsers render their drawing the same way.

  2. That parser asks for a text recognizer. A text recognizer is an engine that produces a document’s text from its pixels. It implements org.apache.tika.parser.enricher.TextRecognizer (which extends ContentEnricher) and advertises the real media types it reads, such as image/png. The parser that met the pixels calls the engine. The engine is never chosen by AutoDetectParser as the parser for the file.

  3. The engine is the one named in the top-level text-recognizers list. With no list, the loader finds one among the loaded parsers (Tesseract, when its binary is on the PATH). An empty list turns OCR off everywhere.

  4. Gates decide whether a given image reaches the engine: the entry filters (_mime-include, _mime-exclude, _min-width, _min-height), a fixed floor that never sends an image under 2 x 2 pixels, and the engine own byte limits (minFileSizeToOcr, maxFileSizeToOcr).

  5. The engine output lands inline in the body of the caller’s document (XHTML, Markdown or plain text, depending on the handler). For a PDF page under the AUTO policy, the engine text replaces the poor text layer of that page, but only when the engine is a text recognizer that actually wrote text.

Two consequences matter in practice. First, OCR needs no change to the parser list: every default parser stays loaded, and the OCR engine is configured beside them. Second, the PDF parser "does not OCR anything itself", in the words of the 4.1.x PDF parser page: it renders pages and hands each image to the recognizer resolved for the render type (image/png by default).

From the book to 4.1

Version How OCR was wired

1.x (Tika in Action)

The book (2011, Tika 1.0) predates OCR in Tika. TesseractOCRParser arrived later in the 1.x line, as an ExternalParser-style wrapper that ran the tesseract program, configured with TesseractOCRConfig and a properties file.

2.x

The OCR parser moved to its own module (tika-parser-ocr-module, part of the standard parsers). Configuration through tika-config.xml <params>, the PDF parser option ocrStrategy, and per-request headers in tika-server.

3.x

Same model. Tesseract advertised pseudo media types (image/ocr-png, image/ocr-jpeg and so on) that the image and PDF parsers used to route pictures to it. Timeouts were in seconds (timeoutSeconds, the parser’s timeout parameter).

4.0

JSON configuration (tika-config.json), Tesseract configured as an entry under parsers, timeouts in milliseconds (timeoutMillis), the PDF ocr.strategy block, in-process Tesseract through the new Tess4J module, VLM parsers as an OCR alternative, and the X-Tika-OCR* request headers removed from tika-server.

4.1

The top-level engines map and text-recognizers list choose the engine; the image/ocr-* pseudo-types are retired (refused in configuration, tolerated with a WARN from third-party engines until 5.0); the shared pages block replaces the PDF ocr block. An engine under parsers is deprecated and unsupported from 4.2.0.

The 4.x migration converter rewrites 3.x XML for you, including the old otherTesseractSettings list. The 3.x forms are shown in labelled blocks below where they help to read old configuration.

Installing Tesseract and language packs

TesseractOCRParser runs the tesseract command-line program in a separate process, so the program must be installed where the parsing JVM runs: the application host for an embedded Tika, the forked workers for Tika Pipes and tika-server (they inherit the host), or the container image.

Platform Install the engine and the languages of the Bookshelf corpus (English, Spanish, French)

Debian, Ubuntu

apt-get install tesseract-ocr tesseract-ocr-eng tesseract-ocr-spa tesseract-ocr-fra

RHEL, Fedora

dnf install tesseract tesseract-langpack-eng tesseract-langpack-spa tesseract-langpack-fra

macOS (Homebrew)

brew install tesseract (English and a few defaults) plus brew install tesseract-lang for the other languages

Windows

An installer from the Tesseract project’s download page (the Tesseract documentation links community builds); add the install directory to PATH or set tesseractPath.

Docker

The apache/tika:<version>-full image, which already contains Tesseract with eng, ita, fra, spa, deu and jpn. See The -full Docker image below.

The package names are those of the distribution repositories and the Tesseract installation documentation; they were not installed for this page. Check what you have:

tesseract --version          # engine and Leptonica versions
tesseract --list-langs       # the language models Tika can name
tesseract --help-psm         # the page segmentation modes of your build

Language data (tessdata)

Each language is a .traineddata file in a tessdata directory. Three sets are published by the Tesseract project: tessdata_fast (smaller and faster, what most distribution packages ship), tessdata_best (slower, more accurate) and the legacy tessdata (also needed for the legacy engine mode). Script models such as Latin read every language of a script; the Tika documentation estimates them at roughly 1.6 times the time of eng per page.

Tesseract finds the directory through its compiled-in default or the TESSDATA_PREFIX environment variable. Tika sets TESSDATA_PREFIX for the child process when you configure tessdataPath, or tesseractPath followed by tessdata when only tesseractPath is set (read in the 4.1.0 setEnv method). Use those options instead of a global variable when several Tesseract installations coexist.

Checking from Java that Tika sees Tesseract

TesseractOCRParser probes the binary once, in initialize(). When the probe fails, the parser advertises no media types and does nothing, so a missing install is easy to miss. This check runs the probe:

import org.apache.tika.parser.ParseContext;
import org.apache.tika.parser.ocr.TesseractOCRParser;

public class TesseractCheck {

    public static void main(String[] args) throws Exception {
        TesseractOCRParser tesseract = new TesseractOCRParser();
        tesseract.initialize(); // probes the binary, as the config loader does
        System.out.println("hasTesseract=" + tesseract.hasTesseract()
                + " types=" + tesseract.getSupportedTypes(new ParseContext()));
    }
}

Output on the test machine, which has no Tesseract:

hasTesseract=false types=[]

With Tesseract installed the set lists image/png, image/jpeg, image/tiff, image/bmp, image/gif, image/jp2, image/jpx and image/x-portable-pixmap (the SUPPORTED_TYPES constant of the 4.1.0 source).

The -full Docker image

The official images come in two flavours. apache/tika:<version> holds a JRE and tika-server with every pure-Java parser. apache/tika:<version>-full adds the external tools some parsers shell out to: Tesseract with the eng, ita, fra, spa, deu and jpn language packs, GDAL, ImageMagick (used by the optional Tesseract preprocessing), ffmpeg and font sets. The documentation’s rule of thumb: "Take -full if you OCR scanned documents".

docker run -d --name tika-ocr -p 127.0.0.1:9998:9998 apache/tika:4.1.0-full

# a scanned PDF: the AUTO policy OCRs its image-only page
curl -T scanned-loan-form.pdf -H "Accept: text/plain" http://localhost:9998/tika/text

# a book cover: image-parser reads EXIF, Tesseract reads the printed title
curl -T book-cover.jpg http://localhost:9998/rmeta/text

These commands follow the 4.1.x server and Docker pages; they were not run here (no Tika image was available), so no output is shown. With the image running, /rmeta output names the engine in tk:parsed-by for an image, and the PDF reports pdf:ocr-page-count.

The image is built on Ubuntu, runs as UID 35002, and its Dockerfile takes the language list as a build argument (LANGUAGES='eng ita fra spa deu jpn'). To add a language, extend the image:

FROM apache/tika:4.1.0-full
USER root
RUN apt-get update \
    && DEBIAN_FRONTEND=noninteractive apt-get install --yes --no-install-recommends \
       tesseract-ocr-por tesseract-ocr-cat \
    && rm -rf /var/lib/apt/lists/*
USER 35002

The Dockerfile on the main branch also sets OMP_THREAD_LIMIT=1, with the comment that parallelism comes from the forked parse workers rather than from Tesseract threads. Check the Dockerfile of the tag you deploy; when you build your own image, set the same variable (see Performance and timeout tuning).

Pin the immutable <version>-<N>-full tags in production, bind to 127.0.0.1 unless you mean to publish the port, and mount a tika-config.json to configure OCR:

docker run -d -p 127.0.0.1:9998:9998 \
  -v "$(pwd)/tika-config.json:/tika-config.json" \
  apache/tika:4.1.0-full -c /tika-config.json

Dependencies

TesseractOCRParser lives in tika-parser-ocr-module, which is part of tika-parsers-standard-package, so the usual dependency set already contains it (see Setup & Installation). Only the Java side is on Maven Central; the tesseract program is installed separately as above.

The Tess4J parser is a separate, opt-in artifact under tika-parsers-ml (checked in its 4.1.0 POM):

<dependency>
    <groupId>org.apache.tika</groupId>
    <artifactId>tika-parser-tess4j-module</artifactId>
    <version>4.1.0</version>
</dependency>
dependencies {
    implementation("org.apache.tika:tika-core:4.1.0")
    implementation("org.apache.tika:tika-parsers-standard-package:4.1.0")
    // only for in-process OCR, see "The Tess4J in-process parser"
    implementation("org.apache.tika:tika-parser-tess4j-module:4.1.0")
}

Selecting the engine: the text-recognizers list

Tika 4.1 separates configuring an engine from choosing it. The engine’s options go under the top-level engines map, keyed by a name you pick; the top-level text-recognizers list names the engines that recognise text:

{
  "engines": {
    "tesseract": {
      "tesseract-ocr-parser": {
        "language": "eng+spa+fra",
        "timeoutMillis": 120000
      }
    }
  },
  "text-recognizers": [
    { "engine": "tesseract" }
  ]
}

The rules, from the 4.1.x configuration page:

Situation What happens

An entry {"engine": "<name>"}

The engine configured under engines.<name> recognises text for the types it advertises. Every default parser stays loaded; parsers is not needed.

An entry with the engine inline, {"tesseract-ocr-parser": {...}}

The 4.0 form. Still works and takes the same underscore keys.

Entry keys besides engine

Only _mime-include, _mime-exclude, _min-width and _min-height. The size filters use the image’s recorded tiff:ImageWidth and tiff:ImageLength; an image of unknown size is sent.

Several entries match a type

Every matching entry runs, in list order, and their text is concatenated. A WARN at startup names them, since it is almost never intended.

A type no entry matches

No OCR for that type. The list is authoritative: a classpath engine you did not name is never used.

A named engine advertises no types at startup

Configuration fails to load (for example, the tesseract binary is missing). A _mime-include list cannot stand in for an unreachable engine.

"text-recognizers": []

OCR off everywhere.

No text-recognizers key

The loader resolves one engine per media type among the loaded parsers and logs the choice, so Tesseract on the PATH OCRs images with no configuration at all. An engine under parsers beats a classpath one; among equals the last registered wins, with a WARN naming the collision.

An engine under parsers (the 4.0 shape)

Deprecated since 4.1.0, unsupported in 4.2.0. It configures the engine and makes it a parser for types nobody else claims ("parsers": [{"tesseract-ocr-parser": {}}] alone sends every image straight to OCR). A WARN explains which happened.

Engine types shipped in 4.1: tesseract-ocr-parser, tess4j-parser, openai-vlm-parser, claude-vlm-parser and gemini-vlm-parser. At startup Tika logs one line per effective engine with the media types it enriches, so the engine in use is never a guess.

What a named but missing engine does

Naming the engine is the safe choice because a broken install fails loudly. This program loads a configuration and parses the Bookshelf files given on the command line:

import java.nio.file.Path;

import org.apache.tika.config.loader.TikaLoader;
import org.apache.tika.io.TikaInputStream;
import org.apache.tika.metadata.Metadata;
import org.apache.tika.parser.ParseContext;
import org.apache.tika.parser.Parser;
import org.apache.tika.sax.BodyContentHandler;

public class OcrIntake {

    public static void main(String[] args) throws Exception {
        // args[0]: the tika-config.json, args[1..]: the Bookshelf files to parse
        TikaLoader loader = TikaLoader.load(Path.of(args[0]));
        Parser parser = loader.loadAutoDetectParser();

        for (int i = 1; i < args.length; i++) {
            Path file = Path.of(args[i]);
            Metadata metadata = new Metadata();
            ParseContext context = loader.loadParseContext();
            BodyContentHandler handler = new BodyContentHandler(-1);
            try (TikaInputStream tis = TikaInputStream.get(file, metadata)) {
                parser.parse(tis, handler, metadata, context);
            }
            System.out.println("== " + file.getFileName());
            System.out.println("text: [" + handler.toString().strip() + "]");
            for (String key : new String[] {"Content-Type", "pdf:ocr-page-count",
                    "pdf:chars-per-page", "tk:parsed-by-full-set"}) {
                if (metadata.get(key) != null) {
                    System.out.println(key + " = " + String.join(", ", metadata.getValues(key)));
                }
            }
        }
    }
}

Run with the engines and text-recognizers configuration above (with "language": "eng") on a machine without Tesseract, it stops at loadAutoDetectParser():

Exception in thread "main" org.apache.tika.exception.TikaConfigException: Text recognizer engine "tesseract" advertises
no media types (a _mime-include list does not count). Is the engine unavailable (missing native binary, unreachable
inference server) or configured to skip enrichment?

Run with { "text-recognizers": [] } (OCR off) on a generated one-page image-only scanned-loan-form.pdf and a generated book-cover.jpg:

== book-cover.jpg
text: []
Content-Type = image/jpeg
tk:parsed-by-full-set = org.apache.tika.parser.DefaultParser, org.apache.tika.parser.image.JpegParser
== scanned-loan-form.pdf
text: []
Content-Type = application/pdf
pdf:ocr-page-count = 1
pdf:chars-per-page = 0
tk:parsed-by-full-set = org.apache.tika.parser.DefaultParser, org.apache.tika.parser.pdf.PDFParser

Two details are worth noticing. pdf:ocr-page-count is 1 even with OCR off: it counts the pages that tripped the OCR verdict, whether or not an engine ran, which makes it a good signal for "this PDF needs OCR" in a pipeline that does OCR in a second, slower lane. And the empty text on both files is what an index silently receives when OCR is not configured, which is why a named engine that fails at startup is better than one that is quietly missing.

With Tesseract installed, the same program on the same files is expected to print the recognised text, pdf:ocr-page-count = 1, and TesseractOCRParser among the parser names (for the PDF in tk:parsed-by-full-set, since the engine ran on a page render); that run was not possible here.

Filtering types between engines

Entries can split the work. Here Tesseract reads TIFF scans and PNG page renders, while JPEG photos (book covers, in this corpus mostly decorative) are skipped, and images smaller than a postage stamp never reach the engine:

{
  "engines": {
    "tesseract": { "tesseract-ocr-parser": { "language": "eng+spa+fra" } }
  },
  "text-recognizers": [
    {
      "engine": "tesseract",
      "_mime-exclude": ["image/jpeg"],
      "_min-width": 100,
      "_min-height": 40
    }
  ]
}

The _mime-include and _mime-exclude lists name real types such as image/tiff; the pre-4.1 image/ocr-* spellings are refused at load. The 4.1.x custom-engine page shows the same filters used to keep Tesseract for the formats a second engine does not read.

Turning OCR off

Scope How

The whole deployment

{ "text-recognizers": [] } in tika-config.json. The -full image keeps its parsers but stops OCR.

One request

{ "parse-context": { "text-recognizers": { "enabled": false } } } as request configuration, or, in Java, context.set(TextRecognizerSelection.class, selection) with setEnabled(false). No engine is reported missing.

PDF pages only

"pages": { "text": "EXTRACT" }: pages are never rendered for OCR; images are still OCR’d.

One engine at run time

"skipOcr": true in the engine options (a kill-switch read per parse).

3.x

skipOcr on TesseractOCRConfig, ocrStrategy no_ocr on the PDF parser, or the X-Tika-OCRskipOcr header in tika-server (removed in 4.x).

TesseractOCRParser configuration

Component name tesseract-ocr-parser, class org.apache.tika.parser.ocr.TesseractOCRParser, options bean TesseractOCRConfig. The parser extends AbstractExternalProcessParser and implements Initializable and TextRecognizer. Every option and its default, as read from the 4.1.0 source:

Option Default Meaning and validation

language

eng

Tesseract language names joined with +, for example eng+spa+fra. Three-letter codes, compound codes (chi_tra_vert, aze_cyrl) and script models (Latin, script/Latin). Syntax is checked when set; each name is checked against --list-langs only when preloadLangs is on.

pageSegMode

"1"

Tesseract --psm, a string "0" to "13". Anything else throws "Invalid page segmentation mode".

timeoutMillis

120000

Maximum wait for one Tesseract process, in milliseconds (clipped to the time the task has left). In 3.x this was timeoutSeconds, a renamed key that 4.x rejects.

maxFileSizeToOcr

2147483647 (Integer.MAX_VALUE)

Images larger than this many bytes are not OCR’d.

minFileSizeToOcr

0

Images smaller than this many bytes are not OCR’d.

outputType

TXT

TXT or HOCR. hOCR keeps word boxes in the XHTML.

pageSeparator

""

Inserted between pages of a multi-page image (TIFF). Empty overrides Tesseract 4 and 5’s form feed. Only letters, digits, -, _, / and . are accepted.

preserveInterwordSpacing

false

Passes preserve_interword_spaces=1 (or 0).

otherTesseractConfig

empty map

Extra Tesseract variables, each passed as -c name=value. Names and values are restricted to letters, digits, -, _, / and .. Operator configuration only (see Security).

inlineContent

false

Write the OCR text of an embedded image into the parent document’s handler as well, so it appears where the image sits.

skipOcr

false

Kill-switch read per parse.

preloadLangs

false

Run tesseract --list-langs at startup and validate the configured languages against it.

tesseractPath

""

Directory of the tesseract binary (empty: on PATH). Must exist if set.

tessdataPath

""

Directory of the language data; exported as TESSDATA_PREFIX to the child process.

imageMagickPath

""

Directory of ImageMagick (convert, or magick on Windows). Optional.

enableImagePreprocessing

false

Run ImageMagick on the image before OCR, using the five options below.

density

300

Resolution of the preprocessed image in dpi; 150 to 1200.

depth

4

Bits per sample of the preprocessed image; one of 2, 4, 8, 16, 32, 64, 256, 4096.

colorspace

gray

ImageMagick colorspace (letters, digits, -, _).

filter

triangle

ImageMagick resize filter: point, hermite, cubic, box, gaussian, catrom, triangle, quadratic or mitchell.

resize

200

Scale percent applied during preprocessing; 100 to 900.

applyRotation

false

Compute the skew and rotate with ImageMagick before OCR; records tess:rotation. Needs ImageMagick.

The inline comment of timeoutMillis in the official "Full Configuration" block says "Max seconds", but the name, the setter’s Javadoc and the 4.1.0 source all use milliseconds. Write milliseconds.

A configuration for the Bookshelf scans

The loan forms are typed in English, the reviews are in Spanish and French, and some scans are skewed. This configuration names all three languages, keeps interword spacing for the form fields, turns on ImageMagick preprocessing (available in the -full image) and gives slow scans three minutes:

{
  "engines": {
    "tesseract": {
      "tesseract-ocr-parser": {
        "language": "eng+spa+fra",
        "pageSegMode": "1",
        "timeoutMillis": 180000,
        "preserveInterwordSpacing": true,
        "enableImagePreprocessing": true,
        "applyRotation": true,
        "density": 300,
        "colorspace": "gray",
        "minFileSizeToOcr": 1024,
        "maxFileSizeToOcr": 52428800,
        "otherTesseractConfig": {
          "textord_noise_hfract": "0.15625"
        }
      }
    }
  },
  "text-recognizers": [
    { "engine": "tesseract", "_min-width": 100, "_min-height": 40 }
  ],
  "parse-context": {
    "pages": { "text": "AUTO", "ocr": { "maxPages": 50 } },
    "timeout-limits": { "totalTaskTimeoutMillis": 900000, "progressTimeoutMillis": 120000 }
  }
}

Every key here appears in the 4.1.x documentation or in the 4.1.0 source; this exact file was not loaded against a running Tesseract. Listing three languages makes Tesseract consider all three models on every image, which costs time (see Performance); when the language of a document is known in advance, set it per request instead.

The 3.x equivalent, in a labelled block for readers migrating an old tika-config.xml (parameter names from the 3.3.2 source: the parser’s timeout parameter is in seconds and otherTesseractSettings is a list of space-separated pairs):

<!-- Tika 3.x only -->
<properties>
  <parsers>
    <parser class="org.apache.tika.parser.DefaultParser"/>
    <parser class="org.apache.tika.parser.ocr.TesseractOCRParser">
      <params>
        <param name="language" type="string">eng+spa+fra</param>
        <param name="timeout" type="int">180</param>
        <param name="preserveInterwordSpacing" type="bool">true</param>
        <param name="enableImagePreprocessing" type="bool">true</param>
        <param name="otherTesseractSettings" type="list">
          <string>textord_noise_hfract 0.15625</string>
        </param>
      </params>
    </parser>
    <parser class="org.apache.tika.parser.pdf.PDFParser">
      <params>
        <param name="ocrStrategy" type="string">auto</param>
        <param name="ocrDPI" type="int">300</param>
      </params>
    </parser>
  </parsers>
</properties>

Language

The language option is passed to Tesseract as -l. Guidelines:

  • Name only the languages you expect. Each extra model costs time and can lower accuracy, because Tesseract has more candidate words for every glyph.

  • Use a script model (Latin, Cyrillic, Japanese_vert) when the language varies per document and is not known in advance. Write script/Latin when the model is in the script/ subdirectory of tessdata, and Latin when it sits at the top level, which is where the Debian and Ubuntu tesseract-ocr-script-* packages put it. tesseract --list-langs prints the spelling that resolves.

  • Choose a per-request language when you know it, for example from the language of the surrounding text or from upload metadata. Language detection runs on text, so it cannot pick the OCR language of an image before OCR; see Language Detection & Translation.

  • A syntax error fails at configuration time; a language whose model is missing is caught at startup only with preloadLangs: true, otherwise the Tesseract process fails at parse time and Tika reports "TesseractOCRParser bad exit value".

Page segmentation mode

pageSegMode is Tesseract’s --psm: how Tesseract splits the image into blocks, lines and words before it recognises them. Tika’s default is 1 (automatic with orientation and script detection), not Tesseract’s own default 3.

Mode Tesseract meaning When it suits the Bookshelf files

0

Orientation and script detection (OSD) only, no text

Diagnose rotated scans. Tika then writes metadata instead of text (see below).

1

Automatic page segmentation with OSD (Tika default)

Mixed scans that may be rotated. Needs the osd model.

3

Fully automatic page segmentation, no OSD (Tesseract default)

Upright pages; slightly cheaper than 1.

4

A single column of text of variable sizes

Single-column forms and letters.

6

A single uniform block of text

A cropped paragraph, a review clipping.

7

A single text line

A cropped title strip, a barcode caption.

11

Sparse text, as much as possible in no particular order

Book covers, posters, forms with scattered fields.

12

Sparse text with OSD

As 11, on rotated images.

13

Raw line, bypassing Tesseract-specific hacks

Single lines that mode 7 mangles.

Modes 2 (segmentation only, not implemented in recent Tesseract), 5, 8, 9 and 10 (vertical block, single word, word in a circle, single character) are rarely useful for documents. Run tesseract --help-psm for the authoritative list of your build.

With "pageSegMode": "0" Tika runs Tesseract with --psm 0 only (no -l, no -c options), reads the .osd output and records these keys, all defined in the 4.1.0 source: tess:page-number, tess:orientation, tess:rotate, tess:orientation-confidence, tess:script and tess:script-confidence. The tess:rotation and tess:image-magick-processed keys come from the ImageMagick preprocessing path. As with any custom key prefix, see Metadata for how registered prefixes coexist with the tk: namespace.

Resolution (DPI)

The Tesseract documentation recommends images of at least 300 dpi and notes that accuracy drops off for text smaller than about 10 points at 300 dpi. Where the resolution comes from depends on the path:

Path Resolution control

A rendered PDF page (or EMF/WMF drawing)

pages.render.dpi, default 300 ("which suits Tesseract", in the words of the 4.1.x Pages page). Lower it to 200 to save time on large clean scans; raise it for small print. pages.render.maxImagePixels (100 million by default) skips oversize renders.

An image file or embedded image

The image’s own pixels. Tika does not rescale it unless preprocessing is on.

With enableImagePreprocessing

ImageMagick resamples to density (default 300 dpi) and scales by resize percent (default 200) before Tesseract runs.

Tess4J

Tess4JConfig.dpi (default 300, 72 to 1200). In the 4.1.0 source this value is stored, but no call that passes it to Tesseract was found; treat it as unverified.

TesseractOCRParser itself passes no --dpi flag (its command line is tesseract <input> <output> --psm <mode> -l <langs> followed by the -c variables and the output type). An image file without resolution metadata makes Tesseract guess; Tesseract’s own user_defined_dpi variable can be set through otherTesseractConfig if you need to fix the assumed resolution. That variable is a Tesseract feature, not a Tika option, and was not tested here.

Preprocessing with ImageMagick

Tesseract applies its own binarisation, but skewed, noisy or low-contrast scans improve with preprocessing. Tika’s built-in preprocessing shells out to ImageMagick on a temporary copy of the image:

  • enableImagePreprocessing: true runs the conversion with density, depth, colorspace, filter and resize.

  • applyRotation: true computes the skew angle (the ImageDeskew class in the module) and rotates; the angle goes to tess:rotation.

  • If ImageMagick is not found, the parser logs a WARN ("Backing off to original file") and runs OCR on the unprocessed image, so a missing ImageMagick never fails a parse.

  • ImageMagick is a second external process per image, with its own CPU cost and its own attack surface on hostile images. Enable it for scan corpora that need it, not by default.

For heavier cleanup (despeckling, dewarping, border removal) preprocess the images before they reach Tika, or write a custom engine that wraps your pipeline (see Writing a custom OCR engine).

Source: Apache Tika 4.1.x, TesseractOCRParser Configuration and the 4.1.0 TesseractOCRParser, ImagePreprocessor and ImageDeskew sources.

hOCR output

"outputType": "HOCR" makes Tesseract produce hOCR, an HTML dialect with a bounding box for every page, block, line and word. Tika passes the hOCR elements through into its XHTML (the parser’s HOCRPassThroughHandler), so an XHTML handler receives the ocr_line and ocrx_word spans with their title="bbox ..." attributes, while a text or Markdown handler receives only the words. Use it when you need word positions (highlighting search hits on a scan, redaction); keep TXT otherwise. Content handlers are explained in Content Handlers & Output Formats.

Validation in practice

TesseractOCRConfig setters reject unsafe or impossible values immediately. This fragment exercises them:

import org.apache.tika.parser.ocr.TesseractOCRConfig;

public class TesseractConfigValidation {

    public static void main(String[] args) {
        TesseractOCRConfig config = new TesseractOCRConfig();
        config.setLanguage("eng+fra+spa");
        System.out.println("lang ok: " + config.getLanguage());
        Runnable[] attempts = {
            () -> config.setPageSegMode("14"),
            () -> config.setLanguage("en-US"),
            () -> config.setDensity(100),
            () -> config.setResize(250),
            () -> config.setPageSeparator("<hr>"),
            () -> config.addOtherTesseractConfig("debug_file", "/tmp/x y")
        };
        for (Runnable attempt : attempts) {
            try {
                attempt.run();
                System.out.println("accepted");
            } catch (IllegalArgumentException e) {
                System.out.println("rejected: " + e.getMessage().replace('\n', ' '));
            }
        }
    }
}

Output (Tika 4.1.0):

lang ok: eng+fra+spa
rejected: Invalid page segmentation mode
rejected: Invalid language code(s): [en-US (invalid syntax)]
rejected: Invalid density value. Valid range of values is 150-1200.
rejected: Invalid resize value. Valid range of values is 100-900.
rejected: <hr> contains illegal characters. If you trust this value, set it with setTrustedPageSeparator
rejected: Value contains illegal characters: /tmp/x y

The same rules apply to JSON, since the loader binds JSON keys through these setters. The resize check accepts multiples of 100 only, which is why 250 fails although it is inside the range named in the message.

Per-request settings

Operators configure the engine once; a request may adjust some of it.

In Java, put a TesseractOCRConfig in the ParseContext. The 4.1.0 parser still reads it when no JSON configuration for tesseract-ocr-parser is in the context ("old-style ParseContext config for backward compatibility" in the source):

import java.nio.file.Path;

import org.apache.tika.config.loader.TikaLoader;
import org.apache.tika.io.TikaInputStream;
import org.apache.tika.metadata.Metadata;
import org.apache.tika.parser.ParseContext;
import org.apache.tika.parser.Parser;
import org.apache.tika.parser.ocr.TesseractOCRConfig;
import org.apache.tika.sax.BodyContentHandler;

public class FrenchReviewScan {

    public static void main(String[] args) throws Exception {
        TikaLoader loader = TikaLoader.load(Path.of("tika-config.json"));
        Parser parser = loader.loadAutoDetectParser();

        TesseractOCRConfig french = new TesseractOCRConfig();
        french.setLanguage("fra");
        french.setPageSegMode("6");          // one uniform block of text
        french.setTimeoutMillis(60_000);

        ParseContext context = loader.loadParseContext();
        context.set(TesseractOCRConfig.class, french);

        Metadata metadata = new Metadata();
        BodyContentHandler handler = new BodyContentHandler(-1);
        try (TikaInputStream tis = TikaInputStream.get(Path.of("review-clipping-fr.png"), metadata)) {
            parser.parse(tis, handler, metadata, context);
        }
        System.out.println(handler);
    }
}

This compiles against 4.1.0; it needs Tesseract with the fra model to produce text, and was not run with an engine. A per-request object replaces the whole engine configuration for that parse, so copy the settings you want to keep. The paths, though, are always those of the parser’s initial configuration: the process command line uses the default config’s tesseractPath.

Through JSON (tika-server /tika/config and /rmeta/config endpoints, tika-grpc parse_context_json), the request’s tesseract-ocr-parser block is bound to TesseractOCRConfig.RuntimeConfig. That class refuses tesseractPath, tessdataPath and imageMagickPath (a TikaConfigException), setTrustedPageSeparator, and any otherTesseractConfig, because Tesseract variables such as debug_file name files the binary opens. Operator configuration (the config file and its presets) is trusted and can set all of them.

Source: Apache Tika 4.1.x, TesseractOCRParser Configuration and the 4.1.0 TesseractOCRParser.getConfig and TesseractOCRConfig.RuntimeConfig sources.

The Tess4J in-process parser

Tess4JParser (component tess4j-parser, module tika-parser-tess4j-module) calls the Tesseract native library inside the JVM through Tess4J and JNA, instead of starting a tesseract process per image. The official page opens with a warning worth repeating: it is for "advanced users only". A fault in native Tesseract or Leptonica code crashes the whole JVM, and a native memory leak grows the JVM until something kills it.

When to prefer it

Aspect TesseractOCRParser Tess4JParser

How it calls Tesseract

A new tesseract child process per image

The native library in-process via JNA

Startup overhead

Process fork and exec per image

One-time JNA initialisation, then pooled instances

Thread safety

Naturally safe (separate processes)

Safe through a pool of instances (each native handle is single-threaded)

Crash isolation

A Tesseract crash cannot affect the JVM

A native crash takes the JVM down

Dependencies

The tesseract binary on PATH

libtesseract and libleptonica shared libraries plus JNA

Timeouts

The process is killed when its budget expires

The native call cannot be killed; Tika stops waiting and keeps the busy instance out of the pool until it returns

Best for

Safety-first deployments, light OCR

Measured high-throughput batches, run in Docker under Tika Pipes

Choose Tess4J only when profiling shows that process start-up dominates (many small images, such as page renders or email inline pictures), and run it in forked JVMs with Tika Pipes or tika-server, ideally in a container, so a native crash only kills a fork that is then restarted. Do not load it into a long-lived application server.

Native libraries

The Tess4J jar bundles Windows DLLs only. On Linux and macOS install the libraries and point JNA at them:

# Debian / Ubuntu
apt-get install libtesseract-dev libleptonica-dev tesseract-ocr-eng
# RHEL / Fedora
dnf install tesseract-devel leptonica-devel tesseract-langpack-eng
# macOS
brew install tesseract

Set nativeLibPath (prepended to jna.library.path at initialisation), or -Djna.library.path=... on the JVM, to the directory holding the libraries: typically /usr/lib/x86_64-linux-gnu on Debian and Ubuntu, /usr/lib64 on RHEL and Fedora, /opt/homebrew/lib on Apple Silicon and /usr/local/lib on Intel macOS. If JNA cannot find them, the parser advertises no types: named under text-recognizers that fails configuration load with a message naming it.

Tess4J options

{
  "engines": {
    "tess4j": {
      "tess4j-parser": {
        "dataPath": "/usr/share/tesseract-ocr/5/tessdata",
        "nativeLibPath": "/usr/lib/x86_64-linux-gnu",
        "language": "eng+spa+fra",
        "pageSegMode": 1,
        "ocrEngineMode": 3,
        "poolSize": 4,
        "timeoutMillis": 120000,
        "maxImagePixels": 100000000
      }
    }
  },
  "text-recognizers": [ { "engine": "tess4j" } ]
}

The official page shows the engine inline in the list ("text-recognizers": [{"tess4j-parser": {...}}]); the engines form above is the 4.1 recommended shape and takes the same options. It was not loaded here (no native libraries).

Option Default (4.1.0 source) Meaning

dataPath

""

The tessdata directory. Required on Linux and macOS.

nativeLibPath

""

Directory of libtesseract and libleptonica; prepended to jna.library.path.

language

eng

As for TesseractOCRParser, joined with +.

pageSegMode

1

An integer here (a string for TesseractOCRParser).

ocrEngineMode

3

0 legacy, 1 LSTM only, 2 legacy and LSTM, 3 whatever is available. The legacy modes need the legacy tessdata models.

poolSize

2

Tesseract instances in the pool. Set it to the number of threads that parse concurrently; each instance holds native memory.

timeoutMillis

120000

Applied both to waiting for a pooled instance and to each OCR call, clipped to the task’s remaining budget.

dpi

300

Range 72 to 1200. See the note under Resolution.

minFileSizeToOcr

0

Smaller inputs are skipped.

maxFileSizeToOcr

52428800 (50 MB)

Larger inputs are skipped. Note the different default from TesseractOCRParser.

maxImagePixels

100000000

Decoded-image area limit, checked from the header before full decoding: a guard against decompression bombs.

skipOcr

false

Kill-switch.

The official Tess4J page itself shows both 2147483647 and 50 MB for maxFileSizeToOcr; the 4.1.0 source has 50 * 1024 * 1024.

Per request in Java, set a Tess4JConfig on the ParseContext:

import org.apache.tika.parser.ParseContext;
import org.apache.tika.parser.ocr.tess4j.Tess4JConfig;

public class Tess4JPerRequest {

    static ParseContext frenchBlock() {
        Tess4JConfig override = new Tess4JConfig();
        override.setLanguage("fra");
        override.setPageSegMode(6);
        ParseContext context = new ParseContext();
        context.set(Tess4JConfig.class, override);
        return context;
    }
}

In caller-supplied JSON, dataPath and nativeLibPath throw TikaConfigException (they point at native code), and poolSize and maxImagePixels throw IllegalStateException: the pool is sized at initialisation, and the pixel guard must not be raised by a caller.

Engine collisions

Adding tika-parser-tess4j-module to the class path is meant to make Tess4J the OCR engine, but when Tesseract is also live and no text-recognizers list exists, the winner depends on registration order, which is not guaranteed. A WARN at startup (since 4.1.0) names the collision and the winner. Name the engine you want in text-recognizers and the question disappears.

OCR of images versus PDF pages

The same engine serves two very different inputs.

An image (file or embedded) A PDF page

Who calls the engine

image-parser (JpegParser, TiffParser, the PNG and BMP parsers, and so on), after it parsed the metadata

pdf-parser, after it extracted the page’s text layer and rendered the page

What the engine sees

The image bytes as stored (tis.getPath(), spooled to disk at most once)

A render: pages.render decides dpi (300), colour (GRAY), format (PNG) and size bounds

Whether OCR runs

Always, if an entry in text-recognizers matches and the gates pass

Decided per page by pages.text and pages.ocr

Where the text goes

The image’s own body; with inlineContent: true also into the parent document’s body

The page’s <div class="page">; under AUTO it replaces a poor text layer

Typical cost driver

Number of embedded images (signatures, logos)

Number of pages and the render resolution

Signals in metadata

tk:parsed-by names the engine

pdf:ocr-page-count, pdf:chars-per-page, pdf:unmapped-unicode-chars-per-page, tk:parsed-by-full-set

The page text policy

The 4.1 pages block is shared by every parser that renders (in 4.1 the PDF parser and the EMF/WMF parsers). Its text value decides where a page’s text comes from:

pages.text Meaning

EXTRACT

The document’s own text only; pages are never rendered for OCR.

AUTO (default)

The document’s text, plus OCR of each page whose extracted text is poor by the ocr.auto thresholds. The OCR text replaces that page’s text when the engine is a text recognizer that wrote text; otherwise (no engine, ocr.maxPages spent, engine timeout) the extracted text is kept. AUTO with no engine behaves as EXTRACT.

EXTRACT_AND_OCR

Both, on every page. Doubles the text of born-digital pages; useful when a text layer is known to be wrong (bad OCR from a scanner, or glyphs without a Unicode mapping).

OCR

The engine’s output only. Fails the parse when no engine covers the render type, unless the request switched the recognizers off.

NONE

No text from any source; metadata, attachments, renders and inference still happen.

pages.ocr.maxPages caps the pages of one document that may be OCR’d, counted from the first (-1, the default, for no limit). pages.ocr.auto.totalCharsPerPage (default 10) is the character count below which AUTO OCRs a page, and pages.ocr.auto.unmappedUnicodeCharsPerPage (default 10; a fraction of the page’s characters when below 1) flags pages whose glyphs have no Unicode mapping, a typical sign of a broken font where extraction yields garbage.

{
  "parse-context": {
    "pages": {
      "text": "AUTO",
      "render": { "dpi": 300, "imageType": "GRAY", "imageFormat": "PNG", "maxImagePixels": 100000000 },
      "ocr": { "maxPages": 50, "auto": { "totalCharsPerPage": 10, "unmappedUnicodeCharsPerPage": 10 } }
    }
  }
}

The block is read at four layers, later ones winning: parse-context.pages in the config file, pages in a request or preset (which replaces the config’s block entirely), and, overlaid field by field, pages inside the parser’s entry under parsers (for example pdf-parser), and that parser’s pages in a request. The 4.0 spellings still load and land in pages: ocr.strategy (NO_OCR, AUTO, OCR_ONLY, OCR_AND_TEXT_EXTRACTION) became pages.text, ocr.maxPagesToOcr became pages.ocr.maxPages and ocr.dpi became pages.render.dpi. The 3.x PDF parser options were ocrStrategy, ocrStrategyAuto, ocrDPI, ocrImageType and ocrRenderingStrategy (checked in the 3.3.2 PDFParserConfig source).

Source: Apache Tika 4.1.x, Pages: rendering, OCR policy, emitted page images. The PDF side (render engines, renderingStrategy, encrypted and XFA documents) is covered in PDF Parsing.

Choosing the policy from Java

The policy is a PagesConfig object (org.apache.tika.parser.pages) with a TextPolicy enum. This program forces OCR on scanned-loan-form.pdf, first with no engine, then with the recognizers switched off for the request, and finally loads { "parse-context": { "pages": { "text": "EXTRACT" } } } from a file:

import java.nio.file.Path;

import org.apache.tika.config.loader.TikaLoader;
import org.apache.tika.io.TikaInputStream;
import org.apache.tika.metadata.Metadata;
import org.apache.tika.parser.AutoDetectParser;
import org.apache.tika.parser.ParseContext;
import org.apache.tika.parser.Parser;
import org.apache.tika.parser.enricher.TextRecognizerSelection;
import org.apache.tika.parser.pages.PagesConfig;
import org.apache.tika.parser.pages.TextPolicy;
import org.apache.tika.sax.BodyContentHandler;

public class PagePolicy {

    static void parse(Parser parser, ParseContext context, String label) throws Exception {
        Metadata metadata = new Metadata();
        BodyContentHandler handler = new BodyContentHandler(-1);
        try (TikaInputStream tis = TikaInputStream.get(Path.of("scanned-loan-form.pdf"), metadata)) {
            parser.parse(tis, handler, metadata, context);
            System.out.println(label + ": text=[" + handler.toString().strip()
                    + "] pdf:ocr-page-count=" + metadata.get("pdf:ocr-page-count"));
        } catch (Exception e) {
            Throwable root = e;
            while (root.getCause() != null) {
                root = root.getCause();
            }
            System.out.println(label + ": " + e.getMessage() + " / cause: " + root.getMessage());
        }
    }

    public static void main(String[] args) throws Exception {
        AutoDetectParser parser = new AutoDetectParser();

        PagesConfig ocrOnly = new PagesConfig();
        ocrOnly.setText(TextPolicy.OCR);

        ParseContext noEngine = new ParseContext();
        noEngine.set(PagesConfig.class, ocrOnly);
        parse(parser, noEngine, "OCR, no engine");

        ParseContext switchedOff = new ParseContext();
        switchedOff.set(PagesConfig.class, ocrOnly);
        TextRecognizerSelection off = new TextRecognizerSelection();
        off.setEnabled(false);
        switchedOff.set(TextRecognizerSelection.class, off);
        parse(parser, switchedOff, "OCR, recognizers off");

        TikaLoader loader = TikaLoader.load(Path.of("extract-only.json"));
        parse(loader.loadAutoDetectParser(), loader.loadParseContext(), "EXTRACT");
    }
}

Output on the test machine (no OCR engine installed; message wrapped for the page):

OCR, no engine: Unable to extract PDF content / cause: I regret that I couldn't find an OCR engine to handle
image/png. Name one that covers it in "text-recognizers" (a configured list is authoritative), add one to the
classpath when no list is configured, or set "pages": {"text": "EXTRACT"}.
OCR, recognizers off: text=[] pdf:ocr-page-count=1
EXTRACT: text=[] pdf:ocr-page-count=0

The three lines show the contract: OCR without an engine is an error, not a silent empty result; a request that turns recognizers off is allowed to have none; and EXTRACT does not even evaluate the verdict, so pdf:ocr-page-count is 0. The output was produced by running this program against Tika 4.1.0.

Inline images in PDFs: the double-OCR trap

A PDF also stores pictures as image objects. With "extractInlineImages": true on pdf-parser, each one is parsed as an embedded document, so image-parser sends it to the OCR engine like any other image. A scanned page is usually one full-page image, so with both page OCR and inline images on, the page is OCR’d twice: once as a render, once as an inline image. The 4.1.x PDF parser page calls this out. For scan corpora, keep extractInlineImages off and rely on page OCR; turn it on only when you need the pictures themselves (and then consider _min-width/_min-height or _mime-exclude to keep small decorations away from the engine).

Images inside other documents

Pictures in a DOCX, an EML attachment or a ZIP entry are embedded documents; each is parsed by image-parser, which calls the engine. The OCR text then appears in the embedded document’s content: a separate record in /rmeta or RecursiveParserWrapper output, or concatenated into the parent’s text by the default embedded-document handling. Set inlineContent: true on tesseract-ocr-parser to make the text of an image embedded as INLINE (tk:embedded-resource-type) also appear where the picture sits in the parent. See Embedded Documents & Recursive Parsing for how embedded output is collected and limited.

Performance and timeout tuning

OCR is usually the most expensive thing Tika does. The cost is roughly pages or images x pixels per image x languages, plus a process start per image for TesseractOCRParser. Tune in this order:

  1. Do less OCR. Keep AUTO (not EXTRACT_AND_OCR), cap pages.ocr.maxPages for long scans, skip small images with _min-width/_min-height and minFileSizeToOcr, exclude types you do not need (_mime-exclude), and avoid the double-OCR of inline images.

  2. Make each call cheaper. Name one language, not five. Prefer tessdata_fast models unless accuracy measurements justify tessdata_best. Render at 200 dpi instead of 300 for large, clean scans, and test the effect on your error rate. Leave ImageMagick preprocessing off unless the corpus needs it. Use pageSegMode 3 instead of 1 when the scans are known to be upright.

  3. Use the cores well. Tesseract can use OpenMP threads inside one process; Tika already runs many documents in parallel (Tika Pipes forks, tika-server workers). Set OMP_THREAD_LIMIT=1 so the two levels do not oversubscribe the CPU, as the -full Dockerfile does, and size the number of forks to the cores.

  4. Separate the lanes. Route documents with pdf:ocr-page-count > 0 (or image types) to a dedicated, OCR-enabled pipeline with longer timeouts, and keep the fast lane OCR-free with { "text-recognizers": [] }.

  5. Consider Tess4J only after measuring that process start-up dominates, and only inside forked JVMs.

The three timeouts

Tika 4.x bounds a parse with three independent settings:

Setting Default Question it answers

timeout-limits.totalTaskTimeoutMillis

3600000 (1 hour)

How long may this document take end to end, including every embedded document and every OCR call.

timeout-limits.progressTimeoutMillis

120000 (2 minutes)

How long may the parse go silent before it is considered hung. A bounded external call such as Tesseract reports progress while Tika waits on it, so a slow OCR call does not trip this.

tesseract-ocr-parser.timeoutMillis (or tess4j-parser.timeoutMillis)

120000

How long may one OCR call take. The effective grant is min(configured, time left in the document).

{
  "engines": {
    "tesseract": { "tesseract-ocr-parser": { "language": "eng", "timeoutMillis": 120000 } }
  },
  "text-recognizers": [ { "engine": "tesseract" } ],
  "parse-context": {
    "timeout-limits": {
      "totalTaskTimeoutMillis": 600000,
      "progressTimeoutMillis": 60000,
      "throwOnDeadline": false
    }
  }
}

How to read the outcomes, summarised from the scenarios on the 4.1.x Timeouts page (a ZIP holding a PDF with three images that need OCR, 10-minute task budget, 60-second stall detector, 120-second Tesseract timeout):

What happens What you see What to change

Each OCR call takes 90 s

Success, full content: the bounded wait counts as progress.

Nothing.

One huge noisy scan needs more than 120 s

The tesseract process is killed at 120 s; that image carries a TikaTimeoutException with "requested=120000ms, granted=120000ms"; the other images parse.

Raise timeoutMillis, or accept losing that image.

A variant with a 5-minute task budget instead of 10: the budget runs out with 30 s left for the last image

The last image is granted only what is left ("requested=120000, granted=30000 (task remaining)"), the document reports PARTIAL_TIMEOUT and the deadline flag.

Raise totalTaskTimeoutMillis; granted below requested means the parser limit was not the problem.

Tesseract ignores the kill (NFS stall, broken build)

First the per-call timeout, then, if the fork goes silent, a stall-detector TIMEOUT. Zombie tesseract processes on the host are the telltale.

No timeout setting helps; fix the environment.

Upgrading from 3.x: timeoutSeconds became timeoutMillis (the old name is rejected as an unknown property), TikaTimeoutException is now a checked TikaException (a catch (RuntimeException e) no longer catches it), and progressTimeoutMillis no longer caps a single external call, so operators who raised it to tolerate slow OCR must raise the OCR timeout instead. Values under one second for any of these are warned as a likely seconds-versus- milliseconds mistake.

Memory and isolation

  • Spooling. Tesseract reads a file, so every OCR’d image or page render is written to a temporary file. Give the forks a fast, roomy temporary directory, and see the spooling notes in Parser API & AutoDetectParser.

  • Render size. A page render at 300 dpi in gray is about 8.7 million pixels for A4; colour or higher dpi grows fast. pages.render.maxImagePixels and Tess4J’s maxImagePixels stop oversize inputs before they exhaust memory.

  • Isolation. TesseractOCRParser crashes stay in the child process. Hangs and memory blow-ups elsewhere in a parse are contained by running parsing in forked JVMs (Tika Pipes, PipesForkParser, tika-server), which restart after a crash or a stall. That is mandatory for Tess4J.

Security

OCR inputs are, by definition, files from somewhere else. The points specific to OCR:

  • Decompression bombs. A small PNG or TIFF can decode to gigapixels. Keep maxImagePixels (render and Tess4J) and maxFileSizeToOcr at sensible values, and keep parsing in forked JVMs with memory limits.

  • External processes. Tesseract and ImageMagick run with the privileges of the Tika process. Run Tika as an unprivileged user (the images use UID 35002), keep the binaries patched, and enable ImageMagick only where needed.

  • Operator-only settings. Paths (tesseractPath, tessdataPath, imageMagickPath, Tess4J dataPath and nativeLibPath) and otherTesseractConfig are locked against per-request configuration, as shown above. Keep the tika-server /config endpoints disabled (the default; allowPerRequestConfig turns them on) and offer vetted presets instead. For example, a server-side "presets": { "no-ocr": { "pages": { "text": "EXTRACT" } } } lets callers ask for /tika/preset/no-ocr/text without any per-request configuration.

  • Extra jars. A custom engine is code; the /tika-extras directory of the images and the tika.extras.dir property are trusted code locations.

  • Cost amplification. An attacker who can upload many image-heavy files can buy a lot of your CPU. Cap pages, images and time per document, and rate-limit uploads (see the OWASP file upload guidance).

OCR through tika-server

With the -full image (or a host with Tesseract), tika-server applies the configuration file’s OCR settings to every request. Request-level control has three forms in 4.x:

# 1. the server default (AUTO policy, configured engine)
curl -T scanned-loan-form.pdf http://localhost:9998/tika/text

# 2. a vetted preset defined in the server's tika-config.json, for example "no-ocr"
curl -T catalogue.pdf http://localhost:9998/tika/preset/no-ocr/text

# 3. per-request configuration, only if the operator set "server": {"allowPerRequestConfig": true}
curl -X POST http://localhost:9998/tika/config/text \
  -F "file=@review-clipping-fr.png" \
  -F 'config={"tesseract-ocr-parser":{"language":"fra","pageSegMode":"6"}};type=application/json'

The endpoints and the multipart file and config parts follow the 4.1.x server page; the third request’s body (a tesseract-ocr-parser block, bound to the runtime-restricted config described above) is inferred from the source and was not sent to a running server. The 3.x X-Tika-OCRLanguage-style headers no longer exist in 4.x.

Writing a custom OCR engine

When neither Tesseract nor a vision-language model fits (a commercial OCR service, a GPU OCR server, your own preprocessing plus Tesseract), write a text recognizer. In outline, from the 4.1.x "Wiring in Your Own OCR Engine" page:

  • A class that implements Parser, TextRecognizer and usually Initializable, annotated @TikaComponent(name = "my-ocr-parser", spi = false) so it runs only when named.

  • getSupportedTypes advertises real image types, and returns an empty set when the engine is unavailable (named under text-recognizers, that fails configuration load); recognizesText returns false when this parse should keep its extracted text.

  • parse writes the text through an XHTMLContentHandler, honours a timeout (external processes through AbstractExternalProcessParser, so the pipes deadline reaches them), reads the image once through tis.getPath(), and never writes tk: keys.

  • Package with tika-annotation-processor (which writes the META-INF/tika/parsers.idx component index), put the jar on the class path or in /tika-extras, and configure it like Tesseract:

{
  "engines": {
    "bookshelf-ocr": { "bookshelf-ocr-parser": { "endpoint": "http://ocr:9000", "apiKey": "${env:BOOKSHELF_OCR_API_KEY}" } }
  },
  "text-recognizers": [ { "engine": "bookshelf-ocr" } ]
}

The ${env:NAME} syntax reads the key from the environment when the configuration loads, so no secret is written in the file. The Java contract of TextRecognizer is marked experimental in 4.1.0 (4.2 batches recognition per document), while the JSON that names the engine is stable. The full walkthrough, with the class diagram and packaging, is in Extending Tika; engines reachable through an OpenAI-compatible endpoint need no code at all, see VLM Parsers & Inference.

Troubleshooting

Symptom Likely cause and fix

Images and scans produce empty text, no error

No engine: Tesseract is not on the PATH of the parsing process (check the forks and the container, not your shell), or OCR was turned off. Name the engine in text-recognizers so a missing binary fails at startup.

TikaConfigException: engine "tesseract" advertises no media types

The named engine is unavailable: missing binary, wrong tesseractPath, or (Tess4J) JNA cannot find the native libraries.

"I regret that I couldn’t find an OCR engine to handle image/png"

pages.text is OCR and no recognizer covers page renders. Configure an engine or use AUTO/EXTRACT.

"TesseractOCRParser bad exit value"

Tesseract failed, often a missing language model (-l fra without fra.traineddata) or an unreadable image. Run tesseract --list-langs; turn on preloadLangs to catch it at startup.

TikaTimeoutException with "requested=…​, granted=…​"

Granted equals requested: raise timeoutMillis. Granted below requested: the task budget ran out; raise totalTaskTimeoutMillis or OCR fewer pages.

Scanned PDF OCR’d twice, duplicated text

Page OCR plus extractInlineImages: true, or two recognizers on one type (look for the startup WARN).

Born-digital PDF text replaced by worse OCR

EXTRACT_AND_OCR or OCR set where AUTO was intended, or ocr.auto thresholds set too high.

WARN about an engine under parsers

The 4.0 shape. Move the options to engines and name the engine in text-recognizers before 4.2.

Unknown property timeoutSeconds

A 3.x key. Use timeoutMillis (milliseconds), or run the migration converter.

Very slow OCR, CPU oversubscribed

Too many languages, tessdata_best, high dpi, or OpenMP threads inside each of many parallel Tesseract processes. Set OMP_THREAD_LIMIT=1 and tune as in Performance.

What was verified and what was not

Verified:

  • Every TesseractOCRConfig option name, default and validation rule in the option table, by reading the 4.1.0 sources jar; the 3.x names (timeoutSeconds, the parser’s timeout parameter, otherTesseractSettings, skipOCR) and the image/ocr-* pseudo-types from the 3.3.2 sources jar; the 3.x PDF ocrStrategy and ocrDPI options from the 3.3.2 PDFParserConfig source.

  • The Tesseract command line Tika builds (--psm, -l, -c variables, no --dpi), the TESSDATA_PREFIX handling, the tess: keys, the ImageMagick fall-back, the per-request RuntimeConfig restrictions and the old-style ParseContext fall-back, from the 4.1.0 TesseractOCRParser source.

  • The Tess4J option names and defaults (including the 50 MB maxFileSizeToOcr and the pageSegMode integer type), the pool and timeout behaviour, and the module’s place under tika-parsers-ml, from the 4.1.0 sources jar and POM.

  • By running against Tika 4.1.0 without Tesseract: the probe output (hasTesseract=false types=[]), the config validation messages, the TikaConfigException for a named but unavailable engine, the { "text-recognizers": [] } parse output with pdf:ocr-page-count = 1, the error for TextPolicy.OCR with no engine, the per-request switch-off, and EXTRACT loaded from JSON. The sample PDF and JPEG were generated with ImageMagick for this page.

  • The -full image’s language list, UID 35002, the Ubuntu base and OMP_THREAD_LIMIT=1, from the official docs and the Dockerfile on the main branch.

Not verified:

  • Any actual OCR: Tesseract was not installed and no Tika Docker image was pulled, so no recognition output, timing, hOCR output, tess: metadata values or tk:parsed-by value naming TesseractOCRParser was observed.

  • The Bookshelf configuration, the Tess4J engines configuration, the filter example and the per-request Java example were not loaded or run against a working engine (the examples compile against 4.1.0 except where stated).

  • The tika-server /tika/config request body for tesseract-ocr-parser (inferred from the source and the documented multipart shape) and the no-ocr preset route were not sent to a running server.

  • Whether Tess4JConfig.dpi affects recognition in 4.1.0: no call passing it to Tesseract was found in the source.

  • The package names per operating system, the Windows installer advice, Tesseract’s user_defined_dpi variable and the page segmentation mode descriptions come from the Tesseract documentation and distribution packages, not from a local install.

  • The Tika 4.1.x documentation says its index was generated with AI assistance and needs human review; where it disagreed with the source (timeoutMillis described as seconds, two different Tess4J maxFileSizeToOcr defaults), this page follows the source.