Configuration

Tika 4.x is configured with a JSON file — parsers, detectors, content handlers, server behavior and the Tika Pipes pipeline. // and /* */ comments are allowed.

Converting a 3.x tika-config.xml? See the Migration Guide, and tika-app’s --convert-config-xml-to-json` flag.

Top-level JSON structure

A tika-config.json is a single JSON object whose keys are the sections below. Every section is optional; anything you omit uses its defaults.

{
  "parsers": [ /* parser declarations */ ],
  "detectors": [ /* detector declarations */ ],
  "encoding-detectors": [ /* encoding detector declarations */ ],
  "metadata-filters": [ /* metadata filter declarations */ ],
  "renderers": [ /* page renderer declarations */ ],
  "text-recognizers": [ /* OCR engines etc., selected by name; see below */ ],
  "translator": { /* translator declaration */ },
  "content-handler-factory": { /* handler type for emitted content */ },
  "auto-detect-parser": { /* AutoDetectParser options */ },
  "parse-context": {
    "timeout-limits": { /* progress + total task timeouts */ },
    "exception-reporting": { /* how much exception detail is reported */ },
    "unpack-config": { /* embedded-byte extraction */ },
    "pages": { /* rendering, OCR policy and page images for every parser that renders; see below */ }
    /* other SelfConfiguring components by component name */
  },
  "server": { /* tika-server options: allowPipes, allowPerRequestConfig, cors, tlsConfig, ... */ },
  "grpc": { /* tika-grpc options */ },
  "pipes": { /* Pipes process management: numClients, parseMode, ... */ },
  "fetchers": { /* named fetcher instances */ },
  "emitters": { /* named emitter instances */ },
  "pipes-iterator": { /* iterator (one per pipeline) */ },
  "pipes-reporters": { /* per-document status reporters */ },
  "plugin-roots": "/path/to/plugins",
  "metadata-list": { /* metadata JSON: Jackson read limits, typedValues */ },
  "service-loader": { /* SPI load-failure policy */ },
  "xml-reader-utils": { /* XML parser pool size */ }
}

That list is exhaustive: an unrecognized top-level key is fatal at load time, and the error names the valid ones.

parsers, detectors, encoding-detectors, metadata-filters, content-handler-factory and parse-context are covered under Topics below. For server see Tika Server; for grpc see Tika gRPC; for pipes, fetchers, emitters, pipes-iterator, pipes-reporters and plugin-roots see Pipes Configuration.

Where a typo is caught, and where it is not

Unknown keys are rejected almost everywhere: at the top level, inside a component’s config body, and inside a FetchEmitTuple. Four places swallow them silently instead, so a misspelling there produces no error and no effect:

  • the metadata-list body — only maxStringLength, maxNestingDepth, maxNumberLength and typedValues are read;

  • the body of a self-configuring component under parse-context — it is handed to the component untouched;

  • _mime-include / _mime-exclude when the value is neither a JSON array nor a string — a bare string is read as a one-element list, but any other shape yields an empty filter rather than a failure;

  • a non-textual entry inside default-parser’s `exclude list — the list expects plain strings, so an object or array entry such as [{"pdf-parser": {}}] is skipped and the component stays enabled. (An unregistered name in that list, and an unknown key in that same block, are both fatal.)

If a setting appears to do nothing, check its spelling against one of those four first.

Typed values in metadata JSON (typedValues)

By default every metadata value in JSON output (tika-app -J, tika-server /rmeta, and the file-system, S3, GCS and Azure emitters) is a string, so tk:chunks arrives as an escaped string that consumers must parse a second time. Opt in to native types:

{ "metadata-list": { "typedValues": true } }

Values of keys registered as INTEGER or REAL are then written as JSON numbers, BOOLEAN as true/false, and JSON keys such as tk:chunks as nested JSON:

{ "audio:channels": 2, "audio:has-drm": false, "tk:chunks": [ { "text": "...", ... } ] }

The key’s type comes from its Property declaration; tika-metadata-schema’s `metadata-keys.json lists it as valueType. A value that does not fit its type ("abc" on an INTEGER key, a JSON value with more than one value on the key) stays a string, as does a JSON value nested deeper than maxNestingDepth allows, so the output always reads back. Only a single-valued object, or an array holding an object or array, is written as JSON: a JSON scalar or an array of scalars would be indistinguishable from an ordinary value or values. Tika’s own readers accept either form; it is off by default because the change in types breaks consumers that expect strings, and an index that maps a key from its first value (as Elasticsearch does) rejects a later document whose value for that key is malformed and stays a string.

The Elasticsearch and OpenSearch emitters always write JSON keys as JSON, whatever this setting.

Component names

Everything you configure is referenced by its component name — a kebab-case identifier such as pdf-parser, file-system-fetcher or unpack-config, derived from the class’s simple name unless the component declares its own. (developers/ calls the derived form a friendly name; it is the same string.) Class names are not accepted in their place: only components registered with @TikaComponent can be instantiated from JSON, which is what stops a config file from loading arbitrary classes. tika-app --list-parser-names prints the registered parser names, and an unknown name fails at load time listing the ones that are registered.

The parsers list and default-parser

A parsers list loads only the parsers it names — every other parser is dropped. To customize one parser while keeping all the others, add a default-parser entry:

{
  "parsers": [
    { "pdf-parser": { "sortByPosition": true } },
    { "default-parser": {} }
  ]
}

Configuring a parser automatically excludes its default copy, so there is no duplication. Omit default-parser only when you want a Tika limited to the parsers you listed.

detectors works the same way, with a default-detector entry. encoding-detectors has a default-encoding-detector, but it must not be mixed with explicit detector entries — see Encoding Detectors.

The text-recognizers list (4.1.0+)

Text recognizers are engines that produce a document’s text from its pixels: an OCR engine run on an embedded image or a rendered PDF page, a VLM prompted to transcribe. A container parser invokes them on bytes it has already parsed. Two recipes cover almost every deployment.

This list and the engines map it names are stable: an entry keeps loading and meaning "this engine recognizes text for these types", and if 4.3 changes the spelling the earlier form loads as an alias for at least one minor release. What may change is when the engine is called and where its text lands: 4.3 batches recognition per document, so text may be placed after the walk rather than as each image is met. The Java contract behind an engine (TextRecognizer) is experimental, with batching meant as an opt-in addition an engine that takes one image at a time never sees; see Wiring in Your Own OCR Engine.

Choose the engine. Configure it once under engines and name it here; every default parser stays loaded, and image-parser and pdf-parser call it:

{
  "engines": {
    "tesseract": { "tesseract-ocr-parser": { "language": "eng" } }
  },
  "text-recognizers": [
    { "engine": "tesseract" }
  ]
}

An entry naming an engine takes only engine, _mime-include, _mime-exclude, min-width, min-height and checks; the engine’s own settings live under engines, where an inference binding can name the same engine. The 4.0 form, the engine inline, still works: { "tesseract-ocr-parser": { "language": "eng" } }, and takes the same keys. _mime-include/_mime-exclude keep their underscore because every component list uses the one spelling; min-width, min-height and checks belong to this list alone, so they need none, and the older _min-width/_min-height still mean the same (setting both spellings of one key fails the load). In the inline form these three are removed from the engine’s settings.

min-width/min-height keep an image whose recorded size (tiff:ImageWidth, tiff:ImageLength) falls short in either dimension away from the engine, in pixels; an image of unknown size is sent. Whatever the entry says, an image under 2 x 2 (a spacer in an email signature) never reaches any engine; the engines' own byte gates (minFileSizeToOcr) are unchanged.

Send the engine fewer images that hold no text. An entry’s checks lists image checks by name, run in order, cheapest first; the first to refuse keeps the image from the engine, whatever the engine is (Tesseract, a VLM, …​), and records skipped:<reason> in tk:ocr-warnings. For an inline image it is recorded on the parent document too, when the output is one metadata object per document (-J, /rmeta, Pipes). A check that cannot read the image admits it. The checks live in the tika-image-processing module (JDK only, no native code):

{
  "engines": { "jina": { "openai-vlm-parser": { "baseUrl": "https://api.jina.ai", "model": "jina-ocr-v1" } } },
  "text-recognizers": [
    { "engine": "jina",
      "checks": [
        { "byte-size": { "minBytes": 100, "maxBytes": 52428800 } },
        { "min-image-size": { "minTotalPixels": 10000, "minShortSidePixels": 16 } },
        { "blank-image-check": { "maxInk": 0.0001, "tolerance": 40 } }
      ] }
  ]
}

byte-size refuses an image shorter than minBytes or longer than maxBytes by its measured length, without reading it, so it belongs first; 0 turns a limit off, and an image whose length is unknown or only declared (by a container or a header) is admitted. min-image-size refuses an image under minTotalPixels in area or whose shorter side is under minShortSidePixels, by the dimensions in its header (it reads only the start of a large file, and the whole file only when the dimensions are not there); minWidthPixels and minHeightPixels ask for more of one dimension on top of minShortSidePixels, and 0 turns a test off. The min-image-size values shown are its defaults, and byte-size has no limit until set: below 100 x 100 an image holds a few characters at most, and a strip such as 11 x 1000 passes the area test but holds no line of text. blank-image-check refuses an image whose share of pixels differing from its most common gray level by more than tolerance is at most maxInk (0.0001 is 0.01 %, below a page with a line of text), measured on a copy of at most 1,000 pixels a side. With despeckle (on by default) a pixel counts only if most of its 3 x 3 neighbourhood differs from the background, so scattered specks, as on a noisy scan, do not hide a blank page: 0.1 % to 5 % salt-and-pepper noise comes out blank, and a stroke of text does not. An image with transparency is blank only if it is blank over both white and black, so dark or light text on a transparent background is kept. A progressive JPEG over 16 million pixels is not decoded (it is admitted): decoding one takes about 3 bytes of memory a pixel outside the Java heap.

A check’s settings need no underscore. min-width and min-height on the entry are a separate, metadata-only size minimum that runs before any check. The checks do not tell a photo from a page of text, and do not detect rotation. Nor do they, or the tk:ocr-warnings codes, catch fluent hallucinations (what they do not catch).

Turn OCR off. An empty list is authoritative:

{ "text-recognizers": [] }

Turn it off for one request. The list stays configured; the request switches it off, in every container, and no engine is missed as an error:

{ "parse-context": { "text-recognizers": { "enabled": false } } }

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

How the list behaves

A recognizer advertises its real media types (image/png, …​) and does not compete with the parser registered for those types: image-parser still parses the image and calls the recognizer. An entry’s _mime-include/_mime-exclude name real types too (image/tiff); the pre-4.1 image/ocr- spellings are refused at load. *Every entry matching a media type runs, in the order listed. Two recognizers on one type both run and their text is concatenated; a WARN at startup names them, since that is almost never intended. The list is authoritative: a media type no configured recognizer matches gets no OCR, never a classpath engine you did not name. A named engine that reports no media types at startup (missing native binary, unreachable inference server) fails config load rather than going silently inert; that check is made against the engine itself, so a _mime-include list cannot stand in for an unreachable engine. Failures are best-effort: one recognizer failing does not stop the others, and every failure is still reported through the parser’s normal exception handling (timeouts abort the chain immediately).

With no list

When the key is absent, the loader resolves one engine per media type from the recognizers among the loaded parsers and injects that, so Tesseract on the classpath OCRs images with no configuration at all. The rules: a VLM parser configured under parsers beats an engine default-parser found on the classpath; among those the last registered wins (user-supplied classes register after Tika’s); an entry’s _mime-include/_mime-exclude applies, and on the default-parser entry it applies to every engine inside it. When several engines in the winning tier claim a type, a WARN at startup names them and the winner; a VLM parser under parsers shadows a classpath engine. Name the engine in text-recognizers to stop depending on any of this. An AutoDetectParser built in code without the loader applies the same rules at parse time.

An engine under parsers

Since 4.2.0 an OCR engine named under parsers (tesseract-ocr-parser, tess4j-parser, the 4.0 way to configure Tesseract) fails config load: configure it under engines and name it in text-recognizers. A VLM parser is also a document parser and may stay there: claude-vlm-parser next to a default-parser that excludes pdf-parser sends whole PDFs to Claude (see VLM Parsers). Named there, it is also the text recognizer for the image types it advertises unless text-recognizers says otherwise.

Replacing extracted text

An engine that produces the document’s text implements org.apache.tika.parser.enricher.TextRecognizer. Only such engines count when a caller asks whether an engine can stand in for text it already has: the PDF parser’s AUTO OCR strategy replaces a page’s extracted text only if the engine recognizes text and actually wrote some, and keeps the extracted text otherwise. The bundled OCR engines declare the capability; a VLM parser declares it through its textRecognizer option (default true). An entry that recognizes no text (a VLM with textRecognizer: false, for one) still runs on the images and pages it is offered and never displaces text; startup warns about it, and 4.3 gives such annotators a list of their own. When the PDF parser also emits its page renders as embedded documents, it enriches each render itself and the embedded copy is not enriched again (see PDF Parser).

For engine authors

Recognizers are found by interface, not by media type: implement org.apache.tika.parser.enricher.ContentEnricher (or TextRecognizer, which extends it) and advertise the real types you enrich; Wiring in Your Own OCR Engine walks through a complete engine. An engine the classpath supplied is never dispatched to: tesseract-ocr-parser advertising image/png does not take PNGs away from image-parser, it is called by it. The image/ocr-* pseudo-types that routed OCR before 4.1 are retired: a third-party engine still advertising them is treated as a text recognizer for the real types, with a WARN at startup, until 5.0. Nothing else in Tika produces them; a tk:content-type-parser-override naming one matches no parser and falls to the empty fallback.

Secrets from the environment

A string value in the startup config may name an environment variable as ${env:NAME}, alone or inside a longer string, and the loader substitutes the variable’s value when the config is read:

{
  "engines": {
    "jina": { "openai-embedding-engine": { "baseUrl": "https://api.jina.ai",
              "apiKey": "${env:JINA_API_KEY}", "model": "jina-embeddings-v5-omni-small" } }
  },
  "emitters": {
    "es": { "es-emitter": { "esUrl": "http://${env:ES_HOST}:9200/tika", "apiKey": "${env:ES_API_KEY}" } }
  }
}

Only that exact form is touched: ${NAME}, $NAME, ${sys:…​} and any other text stay literal, so a prompt or a pattern that happens to contain ${ is safe. A reference to a variable that is not set fails the load with the variable’s name and the JSON path, never a value, so a missing secret stops the server at startup rather than reaching a service as an empty key. A literal value works as before.

The forked parse workers of tika-server and Pipes never read the config file or the environment: the parent resolves every reference once and hands each fork the resolved config over its stdin, so a value that itself contains ${env:…​} is not expanded a second time. Presets written inline in the config file resolve at startup with the rest of it; catalog presets and per-request configuration (/config endpoints, pipes tuples) never do: a request cannot read the server’s environment.

A persistent config store (configStoreType file or Ignite) holds only fetchers and emitters registered at runtime (gRPC SaveFetcher), as the caller sent them. Components from the config file, with their resolved references, are never written to it.

Windows file paths

JSON treats the backslash as an escape character, so path options (tesseractPath, imageMagickPath, …​) need forward slashes (C:/Tools/…​) or escaped backslashes (C:\\Tools\...). A single backslash is a JSON parse error.

Adding extra jars (tika.extras.dir)

To add components — extra EncodingDetector or Parser implementations, or their dependencies — without repackaging the application, drop their jars in a directory and point the tika.extras.dir system property at it:

java -Dtika.extras.dir=/path/to/extras -jar tika-app.jar ...
java -Dtika.extras.dir=/path/to/extras -jar tika-server-standard.jar ...

Every *.jar in that directory joins the classpath Tika’s service loading scans, so SPI-registered components are picked up automatically. The jars are forwarded to the forked JVMs that Pipes and tika-server run, too, so they are present where parsing happens.

This is off by default: nothing is loaded unless tika.extras.dir is set, and there is no implicit default directory. Treat the directory as a trusted code location — anything in it runs with the full privileges of the Tika process, so it must not be writable by less-trusted principals, nor (on a server) reachable by request handling.

In the Docker images, mount a directory to /tika-extras instead. PF4J-managed plugins are a separate mechanism — see Pipes Plugins.

Topics

Parser configuration:

  • Pages — rendering, OCR policy and page images, for every parser that renders

  • PDFParser — PDF parsing options

  • Image Parsers — ICC metadata options for JPEG, TIFF, HEIF, BPG and WebP

  • TesseractOCRParser — OCR for image-based text extraction

  • Tess4J OCR Parser — in-process OCR via tess4j JNA bindings; advanced users only, most should prefer TesseractOCRParser

  • VLM Parsers — Claude, Gemini, OpenAI, Ollama, vLLM

  • External Parser — wrap external tools (ffmpeg, exiftool, …​)

Other configuration: