Configuration

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, 3.x needs Java 11), Spring Boot 4.x on Spring Framework 7 and the Docker image apache/tika:4.1.0-1. Tika 4.x is configured with one JSON file, usually called tika-config.json, read by TikaLoader from the tika-serialization module. The same file configures the library, tika-app, tika-server, tika-grpc and Tika Pipes: which parsers and detectors run, their options, the limits in the ParseContext, the metadata filters, the output format, and, for the services, the server and the pipeline. The 3.x tika-config.xml and the TikaConfig class are gone.

This page covers the file itself: every top-level section, how components are named, the parsers list and default-parser, the TikaLoader API, where a typo is rejected and where it is silently ignored, secrets from environment variables, Windows paths, extra jars, the @TikaComponent model behind all of it, and the migration from 3.x. The per-component options are on their own pages: PDF Parsing for pdf-parser and the pages block, OCR with Tesseract for tesseract-ocr-parser and text-recognizers, VLM Parsers & Inference for engines and inference, and Extending Tika for writing your own parser or detector. The ParseContext mechanics are in Parser API & AutoDetectParser, the metadata keys that the filters work on are in Metadata, the output formats are in Content Handlers & Output Formats and the detectors are in Media Types & Detection.

How this page was checked. The examples were compiled and run against Tika 4.1.0 (tika-core, tika-parsers-standard-package as a pom dependency, tika-serialization, and tika-app on the classpath for the CLI runs). The list of top-level keys, the environment-variable syntax and the TikaLoader methods were read from the 4.1.0 sources (TikaJsonConfig, EnvInterpolator, TikaLoader, TikaComponent, XmlToJsonConfigConverter, TikaCLI) and the tika-serialization 4.1.0 jar. Every error message quoted below is the real output of a run. The sample files are the small Bookshelf stand-ins used across this section: a one-heading manuscript.docx titled "The Lighthouse Keeper" by Ana Torres and a one-page catalogue.pdf titled "Spring catalogue" by Bookshelf Press. The tika-server, tika-grpc, Docker and Pipes sections of the file were not run for this page; they are described from the 4.1.x documentation and named as such.

<dependencies>
    <dependency>
        <groupId>org.apache.tika</groupId>
        <artifactId>tika-core</artifactId>
        <version>4.1.0</version>
    </dependency>
    <dependency>
        <groupId>org.apache.tika</groupId>
        <artifactId>tika-parsers-standard-package</artifactId>
        <version>4.1.0</version>
        <type>pom</type>
    </dependency>
    <!-- TikaLoader lives here, not in tika-core -->
    <dependency>
        <groupId>org.apache.tika</groupId>
        <artifactId>tika-serialization</artifactId>
        <version>4.1.0</version>
    </dependency>
</dependencies>
dependencies {
    implementation(platform("org.apache.tika:tika-bom:4.1.0"))
    implementation("org.apache.tika:tika-core")
    implementation("org.apache.tika:tika-parsers-standard-package")
    implementation("org.apache.tika:tika-serialization")
}

Source: Migrating to Tika 4.x ("TikaLoader is in the tika-serialization module").

Why a configuration file

Without a file, Tika discovers everything through the Java ServiceLoader: every parser, detector and encoding detector on the classpath, with default options and no limits beyond the defaults. That is fine for a first program. A real intake service needs to decide:

  • which formats it accepts, and which parsers it never wants to run (an executable parser for an upload form, say);

  • parser options (PDF text ordering, OCR language, timeouts);

  • limits: how much text, how deep the embedded documents go, how long a parse may take;

  • which metadata fields leave the parse, and under which names;

  • the output format of the extracted content;

  • for tika-server and Pipes, the forked JVMs, the fetchers and emitters, and which risky features are switched on.

In 4.x all of that lives in one JSON object. Two properties set it apart from the 3.x XML:

  1. It is an allow-list. Components are referenced by a registered component name (pdf-parser), never by a Java class name, so a configuration file cannot make Tika instantiate an arbitrary class.

  2. It is strict. An unknown top-level key, an unknown option in a component’s body or an unregistered component name fails at load time with a message that names the valid choices. There are four documented exceptions, listed under Strict validation: where a typo is caught.

The startup file is trusted: it is written by the operator. Per-request configuration that arrives over the wire (a tika-server request body, a Pipes FetchEmitTuple) uses the same JSON shapes but is read in a restricted mode, never reads environment variables and is off by default in tika-server. That distinction matters for every security decision on this page.

From the file to the components

flowchart LR F["tika-config.json (comments allowed)"] --> J["TikaJsonConfig.load"] J --> I["resolve env references in string values"] I --> V["check top-level keys against the known set"] V --> L["TikaLoader"] L --> P["loadParsers / loadAutoDetectParser"] L --> D["loadDetectors"] L --> E["loadEncodingDetectors"] L --> M["loadMetadataFilters"] L --> H["loadContentHandlerFactory"] L --> C["loadParseContext"] P --> R["ComponentRegistry: name to class from META-INF/tika/*.idx"] D --> R M --> R C --> R P --> A["AutoDetectParser (shared, thread-safe)"] D --> A C --> X["ParseContext (one per parse)"] A --> Z["parse(tis, handler, metadata, context)"] X --> Z H --> Z

What happens, in order, verified in the 4.1.0 sources:

  1. TikaLoader.load(path) calls TikaJsonConfig.load, which parses the file with Jackson. // and /* */ comments are accepted (the Bookshelf file below has one).

  2. Still inside TikaJsonConfig.load, EnvInterpolator walks every string value and replaces each ${env:NAME} reference. An unset variable fails the load.

  3. validateKeys() compares every top-level key with a fixed set of 24 names. Anything else fails the load.

  4. The loader then checks that java.io.tmpdir exists and is writable (it fails early, naming the directory, rather than on the first spool) and reads the three global sections (metadata-list, service-loader, xml-reader-utils).

  5. Nothing else is built yet. Each load... method builds its component type on first call, resolving each name through the component registry (the META-INF/tika/*.idx files written by the @TikaComponent annotation processor), and caches the result: a second loadParsers() returns the same object.

  6. loadParseContext() is the exception to the caching: it builds a new ParseContext from the parse-context section on every call, which is what you want, because a context belongs to one parse.

Source: the 4.1.0 sources of TikaJsonConfig, EnvInterpolator and TikaLoader.

The top-level sections

A tika-config.json is a single JSON object. Every section is optional, and an omitted section uses its defaults. The table lists every key that the 4.1.0 loader accepts: this is the KNOWN_KEYS set in TikaJsonConfig, and the same list appears in the error message for an unknown key. The 4.1.x configuration page shows a skeleton that it calls exhaustive, but it leaves out three keys the 4.1.0 code accepts: engines, inference and presets.

Key Shape What it configures Read by

parsers

array

The parsers to load and their options. Naming any parser drops all others unless the list has a default-parser entry.

loadParsers(), loadAutoDetectParser()

detectors

array

The media-type detectors; default-detector restores the SPI-discovered ones.

loadDetectors()

encoding-detectors

array

The charset detectors; default-encoding-detector restores the defaults but must not be mixed with explicit entries.

loadEncodingDetectors()

metadata-filters

array

Filters that rewrite metadata after the parse, in order. No default filter exists.

loadMetadataFilters()

renderers

array

Page renderers (for parsers that render pages for OCR).

loadRenderers()

text-recognizers

array

(4.1.0+) The OCR or VLM engines that recognise text in images and rendered pages, by engine name; [] turns OCR off.

text-recognizer loader, used by the parsers

engines

object

(4.1.0+) Named engine instances (Tesseract, a VLM, an embedding engine) that text-recognizers and inference refer to.

engine loader

inference

object

(4.1.0+) Inference bindings (pages, text, media) dispatched after the parse.

inference loader, hooked into loadAutoDetectParser()

translator

object

The translator implementation (default-translator if absent).

loadTranslator()

content-handler-factory

object

The handler type and write limit for emitted content; Markdown if absent.

loadContentHandlerFactory()

auto-detect-parser

object

AutoDetectParserConfig: throwOnZeroBytes and a content-handler decorator factory.

loadAutoDetectParser()

parse-context

object

Components and configs put into each ParseContext: limits, digesters, password provider, pages, per-parser defaults.

loadParseContext()

presets

object

(4.1.0+) Named, vetted parse-context fragments that tika-server callers select with /tika/preset/{name}.

tika-server

server

object

tika-server options: allowPipes, allowPerRequestConfig, endpoints, cors, tlsConfig, maxRequestSizeBytes, metricsPort, and more.

tika-server

grpc

object

tika-grpc options such as allowPerRequestConfig and allowComponentManagement.

tika-grpc

pipes

object

The Pipes process manager: numClients, forkedJvmArgs, parseMode, emitStrategy, timeouts.

Tika Pipes, tika-server, PipesForkParser

fetchers

object

Named fetcher instances (file-system-fetcher, S3, HTTP, …​).

Tika Pipes plugins

emitters

object

Named emitter instances (file system, Solr, Elasticsearch, …​).

Tika Pipes plugins

pipes-iterator

object

The one iterator that feeds a pipeline.

Tika Pipes

pipes-reporters

object

Per-document status reporters.

Tika Pipes

plugin-roots

string or array

Where the pf4j plugin directories are.

Tika Pipes plugin manager

metadata-list

object

Jackson read limits for metadata JSON: only maxStringLength, maxNestingDepth, maxNumberLength are read.

global, at load

service-loader

object

SPI load-failure policy (see the note below the table).

global, at load

xml-reader-utils

object

The shared XML parser pool: maxEntityExpansions, maxNumReuses, poolSize.

global, at load

Two notes on the table. First, service-loader is accepted and deserialized at load time, but in the 4.1.0 tika-serialization jar its config class (GlobalSettings.ServiceLoaderConfig) has no properties at all (checked with javap), so this page cannot tell you which option it takes; the 3.x attributes dynamic and loadErrorHandler have no documented 4.x equivalent. Second, TikaLoader itself does not read server, grpc, pipes, fetchers, emitters, pipes-iterator, pipes-reporters, plugin-roots or presets. It only accepts them, so one file can serve every process. Their own consumers validate them when they start, so a misspelled option inside pipes is reported by Pipes, not by TikaLoader.load.

Array sections and object sections

The array sections (parsers, detectors, encoding-detectors, metadata-filters, renderers, text-recognizers) are ordered lists. Each element is either a string or an object with one key:

{
  "detectors": [
    "poifs-container-detector",
    { "default-detector": { "exclude": ["misc-ole-detector"] } }
  ],
  "metadata-filters": [
    { "include-field-metadata-filter": { "include": ["tk:content", "Content-Type", "dc:title"] } },
    "date-normalizing-metadata-filter"
  ]
}

The string form creates the component with its defaults; the object form passes the body as its configuration. Object sections (parse-context, content-handler-factory, fetchers, engines, …​) key each entry by name instead, with the configuration object as the value. In fetchers, emitters and engines there is one more level: the outer key is your instance id (covers, es), the inner key is the component name.

{
  "fetchers": {
    "inbox": { "file-system-fetcher": { "basePath": "/srv/bookshelf/inbox" } }
  },
  "parse-context": {
    "commons-digester-factory": { "digests": [ { "algorithm": "SHA256" } ] }
  }
}

Component names

Everything in the file is referenced by its component name: a kebab-case identifier such as pdf-parser, default-detector, output-limits or file-system-fetcher. The @TikaComponent annotation processor derives it from the class’s simple name (PDFParser becomes pdf-parser, ShelfCodeFilter becomes shelf-code-filter) unless the class declares its own name, and writes it into an index file such as META-INF/tika/parsers.idx. The loader looks names up in those index files. A class name is not accepted in its place: the failure message ends with "Arbitrary class names are not allowed for security reasons".

To list the names on your classpath, use tika-app:

java -jar tika-app-4.1.0.jar --list-parser-names
java -jar tika-app-4.1.0.jar --list-detector-names

Output (first lines of the parser list, tab-separated class and name):

org.apache.tika.parser.apple.AppleSingleFileParser	apple-single-file-parser
org.apache.tika.parser.apple.PListParser	p-list-parser
org.apache.tika.parser.asm.ClassParser	class-parser
org.apache.tika.parser.audio.AudioParser	audio-parser
org.apache.tika.parser.audio.MidiParser	midi-parser

With tika-parsers-standard-package 4.1.0 the parser list had 88 lines and the detector list 8. The derived names are not always the obvious ones (p-list-parser, bp-list-detector, poifs-container-detector), so copy them from this output instead of guessing. An unknown name also fails the load with the list of the registered ones, which is the other way to find a name. Names of components outside the parser and detector lists (parse-context entries, metadata filters, digesters) are in the .idx files inside each jar; for tika-core 4.1.0 the parse-context names are embedded-limits, exception-reporting, output-limits, timeout-limits, skip-embedded-document-selector, spooling-strategy, standard-metadata-limiter-factory, auto-detect-parser-config, simple-password-provider, text-recognizers, inference, media, pages, basic-content-handler-factory and sax-output-config.

Source: Apache Tika 4.1.x, Configuration, component names, Apache Tika 4.1.x, tika-app CLI and the META-INF/tika/parse-context.idx file of the tika-core 4.1.0 jar.

The parsers list and default-parser

A parsers list loads only the parsers it names. To change one parser and keep all the others, add a default-parser entry. Configuring a parser automatically excludes its default copy, so a configured pdf-parser next to default-parser does not run twice.

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

exclude on default-parser takes component names and is only needed to disable a parser without replacing it. The Bookshelf upload form never accepts programs, so executable-parser is excluded.

To see what "only the parsers it names" means, the next run loads a file with just pdf-parser and parses both sample files. tk:parsed-by shows which parser ran.

{ "parsers": [ { "pdf-parser": { "sortByPosition": true } } ] }
files/catalogue.pdf -> application/pdf parsedBy=org.apache.tika.parser.CompositeParser,org.apache.tika.parser.pdf.PDFParser chars=68
files/manuscript.docx -> application/vnd.openxmlformats-officedocument.wordprocessingml.document parsedBy=org.apache.tika.parser.EmptyParser chars=0

The manuscript is still detected as a Word document (detection is a separate section), but no parser handles it, so EmptyParser returns no text and no error. Leaving out default-parser by accident is a silent loss of every other format; keep it unless you really want a Tika limited to the listed parsers.

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 encoding-detector entries. See Character Encoding Detection.

MIME filtering with _mime-include and _mime-exclude

Any parser entry can be restricted to some media types, or kept away from some, with two framework directives. The leading underscore marks them as keys read by the loader, not by the parser; the loader strips them and wraps the parser in a MIME-filtering decorator.

{
  "parsers": [
    { "pdf-parser": {
        "_mime-include": ["application/pdf"],
        "_mime-exclude": ["application/pdf+fdf"],
        "sortByPosition": true } },
    { "default-parser": { "_mime-exclude": ["image/heic"] } }
  ]
}

On a default-parser entry, a real-type filter also removes the parser for that type. A bare string is read as a one-element list, but a value that is neither a string nor an array silently becomes an empty filter. Since 4.1.0 the pre-4.1 image/ocr-* pseudo-types are refused in these lists: filter the real type (image/tiff) on the engine’s own entry instead.

This replaces the 3.x <mime> and <mime-exclude> elements, and exclude replaces <parser-exclude>.

Engines under parsers are deprecated

In 4.0 an OCR engine (tesseract-ocr-parser), a VLM parser or the image-embedding parser was configured as a parsers entry. Since 4.1.0 that is deprecated: it still works with a startup WARN that says what the entry does, and it fails config load in 4.2.0. The 4.1 shape names the engine under engines and lists it in text-recognizers, and every default parser stays loaded:

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

"text-recognizers": [] turns OCR off everywhere, and {"parse-context": {"text-recognizers": {"enabled": false}}} turns it off for one request. The engine options are on OCR with Tesseract; the VLM and inference engines are on VLM Parsers & Inference.

The other library sections

metadata-filters

metadata-filters is a top-level array run in order, each filter seeing the output of the previous one. There is no default-metadata-filter and no implicit filtering: an absent or empty section means the metadata is emitted unchanged. The built-in filters in tika-core are include-field-metadata-filter, exclude-field-metadata-filter, field-name-mapping-filter, remove-by-mime-metadata-filter, clear-by-attachment-type-metadata-filter, date-normalizing-metadata-filter, capture-group-metadata-filter, geo-point-metadata-filter, legacy-key-migration-filter and no-op-filter. The filter options and recipes are on Metadata.

Where the filters run is the important part:

  • tika-app, tika-server and Tika Pipes apply the configured chain before writing or emitting output.

  • A library caller applies it itself. AutoDetectParser.parse does not run the filters; call loader.loadMetadataFilters().filter(list, context) on the metadata, as the Bookshelf program below does.

  • In tika-app 4.1.0 the chain was applied to the -J (recursive JSON) output but not to -m or -j. With the Bookshelf include filter, -m still printed every PDF key while -J printed only the four included fields.

  • An include filter drops what it does not name, including tk:content. The same -J run returned no extracted text at all, because the filter’s list did not include tk:content. Add it when the text has to survive.

  • A per-request metadata-filters list in parse-context replaces the configured chain for that request.

Run of -J -t with the Bookshelf filter (include lists Content-Type, dc:title, dc:creator, xmpTPg:NPages):

[{"dc:creator":"Bookshelf Press","dc:title":"Spring catalogue","Content-Type":"application/pdf","xmpTPg:NPages":"1"}]

content-handler-factory

The handler that tika-app, tika-server, Pipes and loadContentHandlerFactory() use for the extracted content. If the section is missing, the loader returns a BasicContentHandlerFactory of type MARKDOWN with no write limit, the 4.x default everywhere (3.x defaulted to XHTML).

{
  "content-handler-factory": {
    "basic-content-handler-factory": { "type": "TEXT", "writeLimit": 500000 }
  }
}

The types are those of BasicContentHandlerFactory.HANDLER_TYPE (TEXT, HTML, XML, BODY, IGNORE, MARKDOWN); see Content Handlers & Output Formats.

Source: the Javadoc of loadContentHandlerFactory in the 4.1.0 TikaLoader source and Migrating to Tika 4.x, default content handler.

auto-detect-parser

The AutoDetectParserConfig, read from the root of the file by loadAutoDetectParser(). In 4.1.0 it has two properties: throwOnZeroBytes (default true: an empty file throws ZeroByteFileException) and contentHandlerDecoratorFactory. The 3.x limits that lived here (outputThreshold, maximumCompressionRatio, maximumDepth, …​) moved to parse-context.

{ "auto-detect-parser": { "throwOnZeroBytes": false } }

parse-context

parse-context holds the objects and configs that each ParseContext from loadParseContext() carries. The ones a Bookshelf worker sets are the limits:

Entry Properties (4.1.0 setters) Purpose

output-limits

writeLimit, throwOnWriteLimit, maxXmlDepth, maxPackageEntryDepth, zipBombThreshold, zipBombRatio

Text size and zip-bomb protection

embedded-limits

maxDepth, throwOnMaxDepth, maxCount, throwOnMaxCount

How deep and how many embedded documents

timeout-limits

totalTaskTimeoutMillis, progressTimeoutMillis, throwOnDeadline

The unified 4.x timeout model

exception-reporting

level (REDACTED, MESSAGE_REDACTED, FULL), maxLength

How much exception detail reaches the output

commons-digester-factory

digests (list of algorithm)

Hashes of each document

pages

text, render, ocr, inference, emit

Page rendering and OCR policy for every rendering parser (see PDF Parsing)

pdf-parser, tesseract-ocr-parser, …​

the parser’s own options

Per-parse defaults for a self-configuring parser

Two kinds of entries behave differently. Plain config objects such as output-limits are deserialized when the context is built, so a misspelled property fails loadParseContext(). Self-configuring components, which include every parser, are left as JSON in the context and resolved by the component when it runs; a typo there surfaces only when that parser parses (see Strict validation: where a typo is caught). When a key has a JSON config, it wins over a value set in code with context.set(XConfig.class, ...).

Source: Apache Tika 4.1.x, Setting limits, Apache Tika 4.1.x, Digesters, Apache Tika 4.1.x, Pages and the tika-core 4.1.0 signatures of OutputLimits, EmbeddedLimits, TimeoutLimits and ExceptionReporting.

translator and renderers

translator takes one translator (loadTranslator() returns DefaultTranslator when absent; tika-core registers default-translator and empty-translator, and tika-translate adds the online ones, covered in Language Detection & Translation). renderers is a list wrapped into a CompositeRenderer; the PDF renderer options are on PDF Parsing.

Source: the registerComponentConfigs() method of the 4.1.0 TikaLoader source and the META-INF/tika/translators.idx file of the tika-core 4.1.0 jar.

The Bookshelf configuration

The Bookshelf intake worker reads one file at startup. It keeps every default parser except the executable parser, orders PDF text by position, keeps four metadata fields, emits Markdown, and caps text, embedded depth and time.

{
  // Bookshelf intake: the startup configuration of every worker
  "parsers": [
    { "pdf-parser": { "sortByPosition": true, "extractInlineImages": false } },
    { "default-parser": { "exclude": ["executable-parser"] } }
  ],
  "detectors": [
    { "default-detector": {} }
  ],
  "metadata-filters": [
    { "include-field-metadata-filter": {
        "include": ["Content-Type", "dc:title", "dc:creator", "xmpTPg:NPages"] } }
  ],
  "content-handler-factory": {
    "basic-content-handler-factory": { "type": "MARKDOWN", "writeLimit": 500000 }
  },
  "auto-detect-parser": { "throwOnZeroBytes": false },
  "parse-context": {
    "output-limits":       { "writeLimit": 500000, "throwOnWriteLimit": false },
    "embedded-limits":     { "maxDepth": 5, "maxCount": 200 },
    "timeout-limits":      { "totalTaskTimeoutMillis": 120000, "progressTimeoutMillis": 30000 },
    "exception-reporting": { "level": "MESSAGE_REDACTED" }
  }
}

The program builds the shared components once and a new ParseContext, Metadata and handler for each document.

import java.nio.file.Path;
import java.util.List;
import org.apache.tika.config.EmbeddedLimits;
import org.apache.tika.config.OutputLimits;
import org.apache.tika.config.TimeoutLimits;
import org.apache.tika.config.loader.TikaLoader;
import org.apache.tika.detect.Detector;
import org.apache.tika.io.TikaInputStream;
import org.apache.tika.metadata.Metadata;
import org.apache.tika.metadata.filter.MetadataFilter;
import org.apache.tika.parser.ParseContext;
import org.apache.tika.parser.Parser;
import org.apache.tika.sax.ContentHandlerFactory;
import org.xml.sax.ContentHandler;

public class BookshelfConfig {

    public static void main(String[] args) throws Exception {
        // 1. read, interpolate and validate tika-config.json once, at startup
        TikaLoader loader = TikaLoader.load(Path.of("tika-config.json"));

        // 2. build the shared, thread-safe components (cached by the loader)
        Parser parser = loader.loadAutoDetectParser();
        Detector detector = loader.loadDetectors();
        MetadataFilter filter = loader.loadMetadataFilters();
        ContentHandlerFactory handlers = loader.loadContentHandlerFactory();

        System.out.println("parser   = " + parser.getClass().getSimpleName());
        System.out.println("detector = " + detector.getClass().getSimpleName());
        System.out.println("filter   = " + filter.getClass().getSimpleName());
        System.out.println("handler  = " + handlers.handlerTypeName());

        for (String name : List.of("catalogue.pdf", "manuscript.docx")) {
            // 3. a fresh ParseContext per document, built from "parse-context"
            ParseContext context = loader.loadParseContext();
            Metadata metadata = Metadata.newInstance(context);
            ContentHandler handler = handlers.createHandler();
            try (TikaInputStream tis = TikaInputStream.get(Path.of("files", name), metadata)) {
                parser.parse(tis, handler, metadata, context);
            }
            List<Metadata> list = List.of(metadata);
            filter.filter(list, context);          // library callers apply the filters themselves
            String text = handler.toString().strip();
            System.out.println("--- " + name);
            System.out.println("limits   = " + OutputLimits.get(context).getWriteLimit()
                    + " chars, depth " + EmbeddedLimits.get(context).getMaxDepth()
                    + ", timeout " + TimeoutLimits.get(context).getTotalTaskTimeoutMillis() + " ms");
            for (String key : metadata.names()) {
                System.out.println(key + " = " + metadata.get(key));
            }
            System.out.println("first line: " + text.lines().findFirst().orElse(""));
        }
    }
}

Output (log lines on standard error omitted):

parser   = AutoDetectParser
detector = DefaultDetector
filter   = CompositeMetadataFilter
handler  = MARKDOWN
--- catalogue.pdf
limits   = 500000 chars, depth 5, timeout 120000 ms
dc:creator = Bookshelf Press
dc:title = Spring catalogue
Content-Type = application/pdf
xmpTPg:NPages = 1
first line: Spring catalogue
--- manuscript.docx
limits   = 500000 chars, depth 5, timeout 120000 ms
dc:title = The Lighthouse Keeper
dc:creator = Ana Torres
Content-Type = application/vnd.openxmlformats-officedocument.wordprocessingml.document
first line: The Lighthouse Keeper

The startup log also has two INFO lines worth knowing: temporary files go to java.io.tmpdir=... (the loader’s temp directory check) and text recognizers: none found among the loaded parsers; images and rendered pages are not enriched, because Tesseract was not installed on the test machine. With Tesseract on the path, that second line names the engine and the media types it covers.

The TikaLoader API

org.apache.tika.config.loader.TikaLoader (module tika-serialization) replaces TikaConfig. Its public methods in 4.1.0, read from the jar with javap:

Method What it does

static load(Path)

Reads, interpolates and validates the file, using the thread context class loader. Throws TikaConfigException or IOException.

static load(Path, ClassLoader)

Same, with an explicit class loader for component lookup (plugin containers, application servers).

static loadDefault()

No file: every component from SPI. Returns one cached, process-wide instance (synchronized).

static loadDefault(ClassLoader)

No file, with this class loader. Builds a new loader on every call.

loadParsers()

The configured parsers as one Parser (a CompositeParser). Cached.

loadAutoDetectParser()

An AutoDetectParser built from loadParsers(), loadDetectors() and auto-detect-parser, with the inference hooks attached when inference is configured. Declared to return Parser. Cached.

loadDetectors(), loadEncodingDetectors()

The configured detectors and charset detectors as one composite each. Cached.

loadMetadataFilters()

The chain as a CompositeMetadataFilter, or a no-op filter if none is configured. Cached.

loadContentHandlerFactory()

The configured factory, or Markdown with no limit. Cached.

loadRenderers(), loadTranslator()

The renderers as a CompositeRenderer; the translator (DefaultTranslator if absent). Cached.

loadParseContext()

A new ParseContext populated from parse-context on each call; an empty context if the section is absent.

loadConfig(Class, T defaults), loadConfig(String key, Class, T defaults)

One config object from parse-context merged over a defaults object you supply; the defaults are not modified.

get(Class)

The generic form behind the load... methods (get(Parser.class), …​). Cached.

getConfig()

The parsed TikaJsonConfig, whose getRootNode() gives the JSON after interpolation.

loadGlobalSettings(), getGlobalSettings()

The metadata-list, service-loader and xml-reader-utils settings (loaded during load).

toJson(), save(File), save(OutputStream)

Writes the loaded components back as pretty-printed JSON. See the caution below.

static getMimeTypes(), static getMediaTypeRegistry()

The shared default MimeTypes and registry.

getClassLoader()

The class loader in use.

How to use it well:

  • One loader per configuration, created at startup. It is cheap to keep and expensive to rebuild (the parser list is built from the registry). The components it returns are meant to be shared: AutoDetectParser, the detectors and the filters are thread-safe in the usual Tika sense. In Spring, expose the loader and the parser as singleton beans; the Spring Boot Integration page of this section covers the lifecycle.

  • A new ParseContext per parse, from loadParseContext(). A context carries per-parse state and must not be shared between concurrent parses.

  • loadDefault() is a process-wide singleton. Use it only where every caller wants the SPI defaults; anything with limits or exclusions needs its own file.

  • Fail at startup. load throws for a syntax error, an unknown top-level key or an unset environment variable, but component names and options are checked only when the matching load... method runs. Call loadAutoDetectParser(), loadParseContext() and loadMetadataFilters() once during startup so a bad entry stops the service before the first document instead of on it.

  • toJson() is a diagnostic, not a round-trip. In 4.1.0 it serialises the parsers' internal state: a file with only { "pdf-parser": { "sortByPosition": true } } came back with defaultConfig and pdfparserConfig blocks of 34 properties each, a renderer and a contentEnrichers object, plus the full SPI detector and encoding-detector lists. Loading that output again failed with Failed to instantiate component 'org.apache.tika.parser.pdf.PDFParser'. Keep your hand-written file as the source of truth. (tika-app’s old --dump-current-config and related options are commented out in the 4.1.0 TikaCLI source with the note "rework with json serialization".)

Source: TikaLoader, Javadoc 4.1.0, Migrating to Tika 4.x, TikaConfig replaced by TikaLoader and the 4.1.0 TikaLoader source (caching, loadDefault, temp directory check).

Strict validation: where a typo is caught

The 4.x loader rejects unknown keys almost everywhere. Each configuration below was loaded with TikaLoader.load(path) followed by loadAutoDetectParser(), loadParseContext() and loadMetadataFilters(); the messages are the real 4.1.0 output, shortened where marked.

Mistake Fails at Message

{ "parser": [ ... ] } (top-level typo)

load

Unknown configuration key(s): [parser]. Valid keys: [parse-context, encoding-detectors, grpc, ...] (all 24 keys)

{ "pdf-parser": { "sortByPositon": true } }

loadParsers

Failed to instantiate component 'org.apache.tika.parser.pdf.PDFParser', caused by Unrecognized field "sortByPositon" (class org.apache.tika.parser.pdf.PDFParserConfig), not marked as ignorable (34 known properties: ...)

{ "pdf-parsr": {} }

loadParsers

Unknown component type: 'pdf-parsr', caused by Component 'pdf-parsr' is not registered. Either the name is misspelled, or the module that provides it is not on the classpath ... 183 registered component(s): ...

exclude naming executabl-parser

loadParsers

Unknown parsers in exclude list: 'executabl-parser'. The exclusion cannot be applied, so the component would stay enabled.

{ "parse-context": { "output-limit": {} } }

loadParseContext

Unrecognized parse-context entry 'output-limit'. Check for a typo; it does not match any registered component.

{ "output-limits": { "writeLimt": 10 } }

loadParseContext

Failed to deserialize component 'output-limits', caused by Unrecognized field "writeLimt" (class org.apache.tika.config.OutputLimits) ... (6 known properties: "maxXmlDepth", "throwOnWriteLimit", ...)

a class name instead of a component name

the matching load...

Unknown type: com.example.bookshelf.ShelfCodeFilter

"C:\Program Files\Tesseract-OCR" (single backslashes)

load

Failed to parse JSON configuration, caused by Unrecognized character escape 'P'

"${env:BOOKSHELF_ES_API_KEY}" with the variable unset

load

environment variable BOOKSHELF_ES_API_KEY is not set; referenced at /emitters/es/es-emitter/apiKey

The 4.1.x documentation names four places that do not reject a typo, so a misspelling there has no effect and no error:

  1. the metadata-list body: only maxStringLength, maxNestingDepth and maxNumberLength are read (the 4.1.0 TikaLoader source confirms it reads exactly those three);

  2. the body of a self-configuring component under parse-context, which is handed to the component untouched;

  3. _mime-include or _mime-exclude with a value that is neither a JSON array nor a string, which yields an empty filter;

  4. a non-string entry inside exclude on default-parser, such as [{"pdf-parser": {}}], which is skipped, so that parser stays enabled.

For the second case this page found the behaviour more nuanced than "no effect". A {"parse-context": {"pdf-parser": {"sortByPositon": true}}} passed every load call. The typo surfaced only when the PDF was parsed, as TikaException: TIKA-198: Illegal IOException from org.apache.tika.parser.pdf.PDFParser caused by the same Unrecognized field "sortByPositon", while the Word document parsed normally. So the mistake is not silent, but it appears per document, at parse time, and only for the formats that parser handles. Parse one sample of each important format in a startup or CI check after changing parse-context parser blocks.

Source: Apache Tika 4.1.x, Configuration, where a typo is caught, and where it is not; the experiments were run for this page against Tika 4.1.0.

Secrets from the environment

Since 4.1.0 a string value in the startup file may reference an environment variable as ${env:NAME}, alone or inside a longer string. The loader substitutes the value when it reads the file. The exact pattern in the 4.1.0 EnvInterpolator source is \$\{env:([A-Za-z_][A-Za-z0-9_]*)}, so a variable name starts with a letter or an underscore and contains letters, digits and underscores.

{
  "server": { "port": 9998 },
  "fetchers": {
    "covers": { "file-system-fetcher": { "basePath": "${env:BOOKSHELF_INBOX}/covers" } }
  },
  "emitters": {
    "es": { "es-emitter": { "esUrl": "http://${env:ES_HOST}:9200/bookshelf",
                            "apiKey": "${env:ES_API_KEY}" } }
  }
}

The rules, checked against the source and with a run:

  • Only ${env:NAME} is interpolated. ${NAME}, $NAME, ${sys:...} and any other text stay literal, so a prompt or a regex that contains ${ is safe. In the test, "${env:BOOKSHELF_INBOX}/covers" became /srv/bookshelf/inbox/covers while a "${BOOKSHELF_INBOX} stays literal" value was left unchanged.

  • Only string values are interpolated, in objects and arrays at any depth. Keys and non-string values (numbers, booleans) are not.

  • An unset variable fails the load with its name and its JSON path, never a value: a missing secret stops the service at startup instead of reaching Elasticsearch as an empty key.

  • The forked parse workers of tika-server and Pipes inherit the parent’s environment and read the same file, so a reference resolves there too.

  • Only the startup file is interpolated. Per-request configuration (tika-server /config endpoints, Pipes tuples) never is: a request cannot read the server’s environment. Inline presets in the file resolve at startup with the rest of it.

  • The reference keeps the secret out of tika-config.json, not out of every file. With a persistent config store (configStoreType file or Ignite) the resolved fetcher and emitter configurations are written to that store; the default in-memory store writes nothing.

  • The Tika 4.1.0 release notes list environment-variable interpolation in tika-config.json as new in 4.1.0, so a 4.0.x loader does not resolve these references. This page did not check whether any 3.x component had a comparable mechanism in the XML; templating the file before start was the general workaround.

Run it with the variables set by the platform (a Kubernetes Secret, a systemd EnvironmentFile, the CI secret store), never with the values in a script:

export BOOKSHELF_INBOX=/srv/bookshelf/inbox
export ES_HOST=search.internal
# ES_API_KEY comes from the secret store, never from this file
java -jar tika-server-standard-4.1.0.jar -c tika-config.json

TikaLoader.getConfig().getRootNode() returns the JSON after interpolation. Do not log it in production: it contains the resolved secrets.

Source: Apache Tika 4.1.x, Configuration, secrets from the environment, the 4.1.0 EnvInterpolator and TikaJsonConfig sources, and the Tika 4.1.0 release notes on the Apache Tika home page ("env-var interpolation in tika-config.json").

Windows file paths

JSON treats the backslash as an escape character. Path options such as tesseractPath or imageMagickPath need forward slashes or doubled backslashes; a single backslash is a JSON parse error (the table above shows the message).

{
  "engines": {
    "tesseract": { "tesseract-ocr-parser": {
        "tesseractPath": "C:/Program Files/Tesseract-OCR/",
        "tessdataPath": "C:\\Program Files\\Tesseract-OCR\\tessdata" } }
  },
  "text-recognizers": [ { "engine": "tesseract" } ]
}

Forward slashes are the safer choice: they also survive being pasted into a shell, a YAML file or a Helm value. The Tesseract options themselves are on OCR with Tesseract; this example’s tesseractPath and tessdataPath names come from the 4.1.x Tesseract page and were not run on Windows for this page.

Adding extra jars: tika.extras.dir

To add components (an extra parser, encoding detector or their dependencies) to tika-app or tika-server without repackaging, put their jars in a directory and point the tika.extras.dir system property at it:

java -Dtika.extras.dir=/opt/bookshelf/tika-extras -jar tika-app-4.1.0.jar --config=tika-config.json catalogue.pdf
java -Dtika.extras.dir=/opt/bookshelf/tika-extras -jar tika-server-standard-4.1.0.jar -c tika-config.json

Every jar in that directory joins the classpath that Tika’s service loading scans, so SPI-registered components (and @TikaComponent names, through their .idx files) are picked up, and the jars are forwarded to the forked JVMs of Pipes and tika-server. The property is off by default and there is no implicit directory. In the Docker images, mount the directory at /tika-extras instead, which is already on the container classpath.

Treat that directory as trusted code: anything in it runs with the full privileges of the Tika process. It must not be writable by less-trusted users, nor reachable by request handling on a server. pf4j-managed Pipes plugins (plugins/, plugin-roots) are a separate mechanism, covered on the Tika Pipes page of this section.

In your own Java application you do not need tika.extras.dir: add the component as a normal dependency.

This page did not run tika.extras.dir; the description comes from the 4.1.x configuration and Docker pages.

The @TikaComponent and serialization model

Behind the JSON is the serialization system of tika-serialization, built on Jackson. The @TikaComponent annotation (org.apache.tika.annotation.TikaComponent, shipped in tika-core) is required for any class that should be configurable from JSON. At compile time the tika-annotation-processor reads it and:

  • derives the component name (or takes name);

  • adds a name=class line to the index file of its type, such as META-INF/tika/metadata-filters.idx;

  • adds the class to META-INF/services/<interface> for ServiceLoader, unless spi = false.

The registry built from the index files is the security allow-list: only registered classes can be instantiated from JSON.

Attribute Default Meaning

name

derived from the simple class name, in kebab case

The component name used in JSON.

spi

true

Also register in META-INF/services. With false the component is only available by explicit configuration and is not loaded by default-parser; use it for opt-in components.

contextKey

auto-detected

The class used as the ParseContext key. Auto-detected for Parser, Detector, EncodingDetector, MetadataFilter, Translator, Renderer, DigesterFactory, ContentHandlerFactory, ContentHandlerDecoratorFactory, MetadataWriteLimiterFactory, UnpackSelector and EmbeddedDocumentExtractor; set it explicitly otherwise, or when a class implements several of them.

defaultFor

none

Marks the component as the default implementation of an interface, used when no explicit configuration is given (for example BasicContentHandlerFactory for ContentHandlerFactory).

Three more ideas complete the model:

  • Self-configuring components. SelfConfiguring is a marker interface. Parser extends it, so every parser reads its own JSON from the ParseContext when it runs, through ParseContextConfig.getConfig(context, "pdf-parser", PDFParserConfig.class, defaultConfig). That is how per-request configuration reaches a parser, and why a typo in a parser block under parse-context surfaces at parse time.

  • Framework directives start with an underscore (_mime-include, _mime-exclude, _min-width, _min-height) so they cannot collide with a component’s own option names. The exclude key of the default-... markers is the exception, because those markers carry only framework keys.

  • Restricted mode for wire input. The startup file is trusted. Per-request JSON (tika-server request bodies, Pipes FetchEmitTuple) is read in restricted mode: the whole tree is scanned before anything is built, and only types that shape this request’s output can be instantiated from the wire (MetadataFilter, ContentHandlerFactory, ContentHandlerDecoratorFactory, DigesterFactory, MetadataWriteLimiterFactory, UnpackSelector). Types with exec, I/O or network capability (Parser, Detector, EncodingDetector, Renderer, Translator, EmbeddedDocumentExtractor) are blocked. The check is fail-closed: a new context-key interface is blocked until it is consciously allow-listed.

The restricted mode does not stop a request from configuring an already-loaded parser, because a parser’s config subtree is passed through unexamined. A request carrying {"parse-context": {"pages": {"text": "EXTRACT_AND_OCR"}}} reaches the PDF parser and takes effect. That is why tika-server gates per-request configuration separately with allowPerRequestConfig, off by default. Treat "callers may send per-request config" as "callers may set any parser option, including ones that start external processes such as OCR".

A custom component: the Bookshelf shelf-code filter

The Bookshelf catalogue files each book under a shelf code built from its title: "The Lighthouse Keeper" goes to SHELF-LIG. A metadata filter is the right place for that rule, because filters are the supported way to shape Tika’s output. The annotation processor has to run at compile time, so the Maven build declares it as an annotation processor path:

<project xmlns="http://maven.apache.org/POM/4.0.0">
  <modelVersion>4.0.0</modelVersion>
  <groupId>com.example.bookshelf</groupId>
  <artifactId>bookshelf-tika-components</artifactId>
  <version>1.0.0</version>
  <properties>
    <maven.compiler.release>21</maven.compiler.release>
    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    <tika.version>4.1.0</tika.version>
  </properties>
  <dependencies>
    <dependency>
      <groupId>org.apache.tika</groupId>
      <artifactId>tika-core</artifactId>
      <version>${tika.version}</version>
    </dependency>
    <dependency>
      <groupId>org.apache.tika</groupId>
      <artifactId>tika-serialization</artifactId>
      <version>${tika.version}</version>
    </dependency>
    <dependency>
      <groupId>org.apache.tika</groupId>
      <artifactId>tika-parsers-standard-package</artifactId>
      <version>${tika.version}</version>
      <type>pom</type>
    </dependency>
  </dependencies>
  <build>
    <plugins>
      <plugin>
        <groupId>org.apache.maven.plugins</groupId>
        <artifactId>maven-compiler-plugin</artifactId>
        <version>3.14.0</version>
        <configuration>
          <annotationProcessorPaths>
            <path>
              <groupId>org.apache.tika</groupId>
              <artifactId>tika-annotation-processor</artifactId>
              <version>${tika.version}</version>
            </path>
          </annotationProcessorPaths>
        </configuration>
      </plugin>
    </plugins>
  </build>
</project>

The Gradle equivalent puts the processor on the annotationProcessor configuration (not run for this page):

dependencies {
    implementation(platform("org.apache.tika:tika-bom:4.1.0"))
    implementation("org.apache.tika:tika-core")
    implementation("org.apache.tika:tika-serialization")
    implementation("org.apache.tika:tika-parsers-standard-package")
    annotationProcessor("org.apache.tika:tika-annotation-processor:4.1.0")
}

The filter extends MetadataFilterBase, whose per-Metadata hook suits a filter that needs no context. Options are plain bean properties, set from the JSON body.

package com.example.bookshelf;

import java.util.Locale;

import org.apache.tika.annotation.TikaComponent;
import org.apache.tika.metadata.Metadata;
import org.apache.tika.metadata.TikaCoreProperties;
import org.apache.tika.metadata.filter.MetadataFilterBase;

/** Derives a shelf code such as "BKS-LIG" from the document title. */
@TikaComponent
public class ShelfCodeFilter extends MetadataFilterBase {

    private String prefix = "BKS";
    private String targetField = "bookshelf:shelf-code";

    public String getPrefix() {
        return prefix;
    }

    public void setPrefix(String prefix) {
        this.prefix = prefix;
    }

    public String getTargetField() {
        return targetField;
    }

    public void setTargetField(String targetField) {
        this.targetField = targetField;
    }

    @Override
    protected void filter(Metadata metadata) {
        String title = metadata.get(TikaCoreProperties.TITLE);
        if (title == null || title.isBlank()) {
            return;
        }
        String letters = title.replaceAll("(?i)^(the|a|an)\\s+", "").replaceAll("[^\\p{L}]", "");
        String code = letters.substring(0, Math.min(3, letters.length())).toUpperCase(Locale.ROOT);
        metadata.set(targetField, prefix + "-" + code);
    }
}

mvn compile generated both files. The index line (license header omitted):

# Generated by TikaComponentProcessor
# Do not edit manually
# Format: component-name=fully.qualified.ClassName[:key=contextKeyClass]
shelf-code-filter=com.example.bookshelf.ShelfCodeFilter:key=org.apache.tika.metadata.filter.MetadataFilter

and META-INF/services/org.apache.tika.metadata.filter.MetadataFilter lists com.example.bookshelf.ShelfCodeFilter. The filter is now configurable by name, before an include filter that keeps the new field:

{
  "metadata-filters": [
    { "shelf-code-filter": { "prefix": "SHELF" } },
    { "include-field-metadata-filter": {
        "include": ["Content-Type", "dc:title", "bookshelf:shelf-code"] } }
  ]
}
package com.example.bookshelf;

import java.nio.file.Path;
import java.util.List;

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

public class ShelfCodeDemo {

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

        ParseContext context = loader.loadParseContext();
        Metadata metadata = Metadata.newInstance(context);
        try (TikaInputStream tis = TikaInputStream.get(Path.of("files/manuscript.docx"), metadata)) {
            parser.parse(tis, new BodyContentHandler(-1), metadata, context);
        }
        filters.filter(List.of(metadata), context);
        for (String name : metadata.names()) {
            System.out.println(name + " = " + metadata.get(name));
        }
    }
}

Output:

bookshelf:shelf-code = SHELF-LIG
dc:title = The Lighthouse Keeper
Content-Type = application/vnd.openxmlformats-officedocument.wordprocessingml.document

Two notes. The filter writes a custom bookshelf: key with Metadata.set; a filter whose target field is configurable and could name a reserved tk: key must use setTrusted instead, as the 4.x serialization page explains (writing a reserved tk: key by string name throws in 4.x). And the same class referenced by its class name, {"com.example.bookshelf.ShelfCodeFilter": {}}, fails with Unknown type: com.example.bookshelf.ShelfCodeFilter: the name is the only way in. Custom parsers and detectors follow the same pattern and are covered on Extending Tika.

Source: Apache Tika 4.1.x, Serialization and Configuration, creating a custom component (its UpperCaseFilter example) and the tika-core 4.1.0 pom.xml, which configures tika-annotation-processor the same way.

Using the file from tika-app, tika-server, Docker and Pipes

The same file is passed to every entry point.

# tika-app (run from the unzipped tika-app-4.1.0.zip, so lib/ sits next to the jar)
java -jar tika-app-4.1.0.jar --config=tika-config.json -t catalogue.pdf

# tika-server (unzipped tika-server-standard-4.1.0.zip); the file must have a "server" section, see below
java -jar tika-server-standard-4.1.0.jar -c tika-config.json

# Docker: mount the file and pass -c after the image name
docker run -d -p 127.0.0.1:9998:9998 \
  -v "$(pwd)/tika-config.json:/tika-config.json" \
  apache/tika:4.1.0-1 -c /tika-config.json

Output of the tika-app line (the TikaCLI class run from the 4.1.0 tika-app jar with its dependencies on the classpath):

Bookshelf spring catalogue
Forty titles from independent publishers.

With a bad file tika-app stops before reading any document: Exception in thread "main" org.apache.tika.exception.TikaConfigException: Unknown configuration key(s): [parser]. Valid keys: [...]. In tika-app 4.1.0 --config is spelled --config=<file>; the batch mode also accepts -c <file>. A single .json argument with no other options is treated as an async (Pipes) configuration, not as a document.

tika-server requires a server section: in 4.1.0 a file without one makes the server refuse to start (TikaConfigException: Couldn't find 'server' element), and an empty "server": {} is enough. So the Bookshelf file above needs that one extra line before it is passed to tika-server or the Docker image. The server also reads the pipes and parse-context sections its forked parsing uses; tika-grpc reads its own grpc section in the same way. Those deployments are covered by the Tika Server (REST API), Docker & Kubernetes Deployment and Tika Pipes pages of this section; the commands above were not run for this page except the tika-app one.

Migrating from 3.x tika-config.xml and TikaConfig

Tika 4.x does not read tika-config.xml at all, and TikaConfig is removed. Migration has three parts: convert the parsers section with the tool, migrate every other section by hand, and replace TikaConfig in code.

flowchart TD X["3.x tika-config.xml"] --> C["tika-app --convert-config-xml-to-json"] C --> P["parsers section as JSON"] X --> M["every other section: migrate by hand"] P --> J["tika-config.json"] M --> J J --> T["TikaLoader.load + loadAutoDetectParser + loadParseContext at startup"] T --> OK{"loads?"} OK -- "no: error names the key or component" --> J OK -- yes --> S["parse one sample per format and compare with 3.x output"] K["3.x code: new TikaConfig(...)"] --> L["4.x code: TikaLoader.load(...)"] L --> T

XML and JSON side by side

Aspect 3.x tika-config.xml 4.x tika-config.json

Root

<properties>

a JSON object

Component reference

full class name, class="org.apache.tika.parser.pdf.PDFParser"

component name, pdf-parser

Parameters

<params><param name="sortByPosition" type="bool">true</param></params>

"sortByPosition": true

Keep the defaults

<parser class="org.apache.tika.parser.DefaultParser">

{ "default-parser": {} }

Disable a parser

<parser-exclude class="..."/>

"exclude": ["executable-parser"] (not needed for a parser you configure)

MIME restriction

<mime>, <mime-exclude>

_mime-include, _mime-exclude

Limits

<autoDetectParserConfig> (outputThreshold, maximumCompressionRatio, maximumDepth, maximumPackageEntryDepth)

parse-context: output-limits, embedded-limits, timeout-limits

Zero-byte handling

<autoDetectParserConfig> throwOnZeroBytes

auto-detect-parser throwOnZeroBytes

Metadata filters

<metadataFilters><metadataFilter class="...">

metadata-filters array

Encoding detectors

<encodingDetectors>

encoding-detectors

Translator, renderers

<translator>, <renderers>

translator, renderers

XML parser pool

<xml-reader-utils>

xml-reader-utils

Service loader

<service-loader dynamic="true" loadErrorHandler="WARN"/>

service-loader (no documented options in 4.1.0)

Server, pipes, fetchers, emitters

separate XML sections and tika-server options

server, pipes, fetchers, emitters, pipes-iterator, pipes-reporters

Locate the file

tika.config system property, then TIKA_CONFIG environment variable, then --config

pass the path: TikaLoader.load(path), --config=, -c

Secrets

no documented interpolation (not checked in the sources)

${env:NAME} (4.1.0+)

Unknown keys

not tested for this page

fatal almost everywhere

The tika.config / TIKA_CONFIG lookup is in the 3.3.2 TikaConfig source. This page found no reference to either name in the 4.1.0 tika-core, tika-serialization and tika-app jars, so do not rely on them in 4.x; the tika-server jar was not checked. The limits row maps the 3.x AutoDetectParserConfig setters to the 4.x OutputLimits and EmbeddedLimits properties by meaning (outputThreshold and maximumCompressionRatio to zipBombThreshold and zipBombRatio, maximumDepth to maxXmlDepth, maximumPackageEntryDepth to maxPackageEntryDepth); that pairing is this page’s reading of the two classes, not an official table, and the 3.x spoolToDisk, digesterFactory, metadataWriteFilterFactory and embeddedDocumentExtractorFactory settings have different 4.x homes (spooling-strategy, commons-digester-factory, standard-metadata-limiter-factory, and a directly bound EmbeddedDocumentExtractor).

Source: Migrating to Tika 4.x, configuration: XML to JSON, Apache Tika 3.3.2, Configuring Tika and the 3.3.2 TikaConfig, ConfigBase and AutoDetectParserConfig sources.

Converting the parsers section

tika-app 4.1.0 has a converter. It writes the JSON to standard output and the diagnostics to standard error, and it takes no other argument:

java -jar tika-app-4.1.0.jar --convert-config-xml-to-json=tika-config.xml > tika-config.json

The Bookshelf 3.x file it was run on (3.x form, kept for migration only):

<?xml version="1.0" encoding="UTF-8"?>
<properties>
  <parsers>
    <parser class="org.apache.tika.parser.DefaultParser">
      <parser-exclude class="org.apache.tika.parser.pdf.PDFParser"/>
      <parser-exclude class="org.apache.tika.parser.executable.ExecutableParser"/>
    </parser>
    <parser class="org.apache.tika.parser.pdf.PDFParser">
      <params>
        <param name="sortByPosition" type="bool">true</param>
        <param name="extractInlineImages" type="bool">true</param>
        <param name="ocrStrategy" type="string">auto</param>
      </params>
    </parser>
    <parser class="org.apache.tika.parser.ocr.TesseractOCRParser">
      <params>
        <param name="language" type="string">eng+spa</param>
        <param name="timeoutSeconds" type="int">120</param>
      </params>
    </parser>
  </parsers>
  <detectors>
    <detector class="org.apache.tika.detect.DefaultDetector"/>
  </detectors>
  <service-loader initializableProblemHandler="throw"/>
</properties>

Standard output:

{
  "parsers" : [ {
    "default-parser" : {
      "exclude" : [ "pdf-parser", "executable-parser" ]
    }
  }, {
    "pdf-parser" : {
      "sortByPosition" : true,
      "extractInlineImages" : true,
      "pages" : {
        "text" : "auto"
      }
    }
  }, {
    "tesseract-ocr-parser" : {
      "language" : "eng+spa",
      "timeoutMillis" : 120000
    }
  } ]
}

Standard error carried a "CONFIGURATION OPTIMIZATION NOTICE" saying that pdf-parser is excluded from default-parser but also configured separately, that the exclusion is redundant, and that it can be removed.

What the run shows:

  • Only parsers is converted. The <detectors> and <service-loader> sections were dropped with no message on either stream. The usage text says so ("parsers section only"), but nothing in the output does: compare section by section.

  • Class names became component names, and exclude lists for <parser-exclude>.

  • Renamed 3.x options were mapped: ocrStrategy became the 4.1 pages.text, and Tesseract’s timeoutSeconds became timeoutMillis, with the value multiplied by 1000. Tesseract’s otherTesseractSettings list becomes the otherTesseractConfig map.

  • The value auto was copied as written. The converted file loaded as it was and parsed both sample files, so the lower-case form is accepted in 4.1.0; the 4.1.x documentation spells the policy values in upper case (AUTO, EXTRACT, …​), which is the safer form to keep.

  • The Tesseract entry stays under parsers, which 4.1.0 accepts with a deprecation WARN and 4.2.0 will reject. On the test machine, with no Tesseract installed, the WARN read: org.apache.tika.parser.ocr.TesseractOCRParser is named under "parsers", which is deprecated for engines since 4.1.0 and unsupported in 4.2.0: configure it under "engines" and name it in "text-recognizers". It advertises no media types (engine unavailable, or configured to skip) and never runs; .... Move it to engines and text-recognizers as shown earlier.

  • A custom or third-party parser class that the converter cannot find in the registry gets a kebab-case name derived from its simple class name, which may not be its real registered name. Check it against --list-parser-names.

The converter class is org.apache.tika.cli.XmlToJsonConfigConverter; its supported parameter types are bool, int, long, double, float, string, list and map. The converted tika-config.json after the manual fixes:

{
  "parsers": [
    { "pdf-parser": { "sortByPosition": true, "extractInlineImages": true, "pages": { "text": "AUTO" } } },
    { "default-parser": { "exclude": ["executable-parser"] } }
  ],
  "engines": {
    "tesseract": { "tesseract-ocr-parser": { "language": "eng+spa", "timeoutMillis": 120000 } }
  },
  "text-recognizers": [ { "engine": "tesseract" } ],
  "detectors": [ { "default-detector": {} } ]
}

This final file follows the 4.1.x documentation for engines and text-recognizers. Note the behaviour change it brings: on the test machine, which had no Tesseract binary, it failed to load with 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?. An engine named in text-recognizers must be usable at startup, while the deprecated entry under parsers was quietly inert. That is the intended 4.1 behaviour (a missing OCR engine stops the service instead of silently producing no OCR text), so install Tesseract, or use the -full Docker image, before deploying the migrated file. It was not loaded with Tesseract installed for this page.

Source: Migrating to Tika 4.x, automatic conversion, the 4.1.0 XmlToJsonConfigConverter source, and a run of the 4.1.0 converter.

Migrating the other sections by hand

A 3.x file with limits and filters (3.x form, not run for this page):

<properties>
  <autoDetectParserConfig>
    <params>
      <outputThreshold>1000000</outputThreshold>
      <maximumCompressionRatio>100</maximumCompressionRatio>
      <maximumDepth>100</maximumDepth>
      <throwOnZeroBytes>false</throwOnZeroBytes>
    </params>
  </autoDetectParserConfig>
  <metadataFilters>
    <metadataFilter class="org.apache.tika.metadata.filter.IncludeFieldMetadataFilter">
      <params>
        <include>
          <field>Content-Type</field>
          <field>dc:title</field>
        </include>
      </params>
    </metadataFilter>
  </metadataFilters>
</properties>

The 4.x equivalent. Note the metadata key names: a 3.x filter that names X-TIKA:content or other 3.x keys has to use the 4.x tk: names (see Metadata, or add legacy-key-migration-filter while consumers catch up).

{
  "auto-detect-parser": { "throwOnZeroBytes": false },
  "parse-context": {
    "output-limits": { "zipBombThreshold": 1000000, "zipBombRatio": 100, "maxXmlDepth": 100 }
  },
  "metadata-filters": [
    { "include-field-metadata-filter": { "include": ["tk:content", "Content-Type", "dc:title"] } }
  ]
}

TikaConfig to TikaLoader in code

3.x form, kept for migration only:

// Tika 3.x: TikaConfig and tika-config.xml (removed in 4.x)
TikaConfig config = new TikaConfig(Path.of("tika-config.xml"));
Parser parser = config.getParser();
Detector detector = config.getDetector();
AutoDetectParser autoDetect = new AutoDetectParser(config);
ParseContext context = new ParseContext();

4.x:

// Tika 4.x: TikaLoader (tika-serialization) and tika-config.json
TikaLoader loader = TikaLoader.load(Path.of("tika-config.json"));
Parser parsers = loader.loadParsers();
Detector detector = loader.loadDetectors();
Parser autoDetect = loader.loadAutoDetectParser();   // declared type is Parser
ParseContext context = loader.loadParseContext();    // limits and per-parser config from the file

// no file: the SPI defaults, as new TikaConfig() was in 3.x
TikaLoader defaults = TikaLoader.loadDefault();

The other code-level changes that come with the move:

  • add tika-serialization to the build if you only had tika-core;

  • a 3.x AutoDetectParser(TikaConfig) constructor call becomes loadAutoDetectParser(); new AutoDetectParser() without a config still works and uses the SPI defaults;

  • JSON config now wins over a programmatic context.set(XConfig.class, ...) for the same key, the reverse of earlier 4.x builds;

  • parseContext.get(SomeConfig.class) no longer returns a config that came from JSON, because configs are cached by component name and class;

  • the Tika facade and new DefaultParser() still work without a loader for simple cases.

The 3.x TikaConfig Javadoc is linked in the references for comparison; there is no 4.1.0 Javadoc page for it.

Memory, performance and robustness

  • Load once. TikaLoader.load parses the file, and the first loadParsers() instantiates every parser in the list (with default-parser, all of them). Do it at startup and share the results; never build a loader per document.

  • Contexts are cheap and per parse. loadParseContext() builds a new context from the parsed JSON each time; that is the intended per-document call.

  • Limits belong in the file. output-limits, embedded-limits and timeout-limits in parse-context are the 4.x defences against huge outputs, zip bombs, deep nesting and parsers that hang. A configuration without them inherits the defaults, which do not cap text (writeLimit -1).

  • Fewer parsers, less risk. Excluding parsers you never need (executables, CAD, fonts for a book intake) reduces the attack surface and the memory that loaded parsers hold. Excluding them is cheaper than catching their failures.

  • Fail fast at startup with the three load... calls and one sample document per important format, as described under strict validation, so a typo in a parser block does not wait for the first PDF.

  • In-process is not isolation. A configuration file does not make in-process parsing safe for untrusted files: use PipesForkParser, tika-server or Pipes, whose forks read the same file.

Pitfalls and security

  • A parsers list without default-parser keeps only the listed parsers; every other format falls to EmptyParser with no error. Shown above with manuscript.docx.

  • ${NAME} is not ${env:NAME}. Only the env: form is interpolated; the others are passed to the component as literal text, so a fetcher would look for a directory literally named ${BOOKSHELF_INBOX}.

  • The resolved secrets are in memory and in persistent config stores. Do not log getConfig().getRootNode(), and do not enable a file-based config store without protecting it.

  • Per-request configuration is parser configuration. allowPerRequestConfig (tika-server) and allowPerRequestConfig / allowComponentManagement (tika-grpc) let a caller set any parser option, including OCR, which starts external processes. Keep them off for untrusted callers; offer presets instead.

  • Include filters drop the content unless tk:content is listed.

  • Library callers must apply the metadata filters themselves; the parser does not.

  • tika-app -m and -j ignored the filter chain in this page’s 4.1.0 run, while -J applied it.

  • toJson() output does not reload in 4.1.0. Keep the hand-written file in version control.

  • The converter silently drops every section except parsers.

  • Engines under parsers work in 4.1.x with a WARN and fail in 4.2.0.

  • Single backslashes in Windows paths are a JSON parse error.

  • tika.extras.dir is a code directory. Anything in it runs with the process’s privileges; make it read-only for the service account.

  • Class names are refused, by design. Do not look for a way around it: register the class with @TikaComponent.

1.x to 4.x at a glance

Topic 1.x (the book, Tika in Action) 2.x 3.x 4.x

File

tika-config.xml

tika-config.xml

tika-config.xml

tika-config.json; XML is not read

Java entry

TikaConfig

TikaConfig

TikaConfig

TikaLoader in tika-serialization

Component reference

class name

class name

class name

registered component name; class names refused

Defaults plus changes

DefaultParser with parser-exclude

Same

Same

default-parser with exclude; configured parsers auto-excluded

Parameters

not checked

not checked

<params> with typed <param>

plain JSON properties, strictly validated

Limits

in code (BodyContentHandler(int))

autoDetectParserConfig (since 2.1.1)

autoDetectParserConfig

parse-context: output-limits, embedded-limits, timeout-limits

Finding the file

not checked

not checked

tika.config, then TIKA_CONFIG

explicit path only (no reference found in the 4.1.0 jars checked)

Secrets

not checked

not checked

not checked

${env:NAME} (4.1.0+)

Registration

META-INF/services

META-INF/services

META-INF/services

@TikaComponent: .idx index files plus META-INF/services

Migration help

n/a

n/a

n/a

--convert-config-xml-to-json (parsers only)

The 3.x cells rest on the 3.3.2 configuration page and the 3.3.2 sources. The 1.x and 2.x cells that are filled in rest on the same sources and the book, and "not checked" means this page did not look at those releases. The AutoDetectParserConfig "since 2.1.1" note is from its Javadoc. Tika in Action predates JSON configuration entirely; its chapter 11 (extending Tika) registers custom parsers through META-INF/services, which @TikaComponent now generates for you.

Checklist

  • Depend on tika-serialization and load the file with TikaLoader.load(Path) once, at startup.

  • Keep default-parser (and default-detector) in the lists unless you want only what you name; exclude parsers you never need by component name.

  • Copy component names from --list-parser-names or from the load error, never guess them; never use class names.

  • Put limits in parse-context: output-limits, embedded-limits, timeout-limits, and exception-reporting for services.

  • Call loadAutoDetectParser(), loadParseContext() and loadMetadataFilters() during startup, and parse one sample per important format in CI after config changes.

  • Use one ParseContext per parse, from loadParseContext().

  • Apply loadMetadataFilters() yourself in library code, and list tk:content in include filters.

  • Reference secrets as ${env:NAME}; never write them into the file, never log the resolved JSON.

  • Use forward slashes in Windows paths.

  • Treat tika.extras.dir as a trusted code location.

  • Register custom components with @TikaComponent and the tika-annotation-processor.

  • Migrating: convert parsers with --convert-config-xml-to-json, migrate every other section by hand, move OCR engines to engines and text-recognizers, switch metadata keys to tk: names, and replace TikaConfig with TikaLoader.