Serialization and Configuration
Tika 4.x uses JSON-based configuration and serialization throughout the system. This document explains how the serialization system works and how to create components that integrate with it.
Overview
Tika’s serialization system provides:
-
JSON Configuration: Configure Tika components using JSON files
-
Friendly Names: Reference components by name (e.g.,
pdf-parser) instead of class names -
ParseContext Serialization: Send per-request configuration via
FetchEmitTuple -
Security: Only registered components can be instantiated from JSON
The system is built on Jackson with custom serializers/deserializers in the
tika-serialization module.
JSON Configuration Format
Tika uses a compact format for component configuration:
{
"auto-detect-parser": {
"throwOnZeroBytes": false
},
"parse-context": {
"commons-digester-factory": {
"digests": [
{ "algorithm": "MD5" },
{ "algorithm": "SHA256" }
]
}
}
}
Inside an array-valued section (parsers, detectors, encoding-detectors,
metadata-filters) an element can be specified as:
-
String:
"pdf-parser"- creates instance with defaults -
Object:
{"pdf-parser": {"ocr": {"strategy": "AUTO"}}}- creates configured instance
Object-valued sections such as parse-context key each entry by name instead, with
the config object as the value.
The @TikaComponent Annotation
The @TikaComponent annotation is required for any class that should be
configurable via JSON. It serves multiple purposes:
-
Registration: Registers the class with a friendly name
-
Index Generation: Creates lookup files for name-to-class resolution
-
SPI Registration: Optionally registers for Java ServiceLoader
-
Security: Acts as an allowlist for deserialization
Basic Usage
@TikaComponent
public class MyCustomParser implements Parser {
// Parser implementation
}
This automatically:
-
Generates friendly name
my-custom-parserfrom the class name -
Adds to
META-INF/tika/parsers.idxfor name lookup -
Adds to
META-INF/services/org.apache.tika.parser.Parserfor SPI
Annotation Attributes
| Attribute | Default | Description |
|---|---|---|
|
(auto-generated) |
Custom friendly name instead of deriving from class name |
|
|
Whether to register in |
|
(auto-detected) |
Class to use as ParseContext key (rarely needed) |
|
(none) |
Marks as default implementation for an interface |
Context Key Detection
When storing components in ParseContext, Tika needs to know which class
to use as the lookup key. For example, CommonsDigesterFactory should be
retrievable via parseContext.get(DigesterFactory.class).
Automatic Detection
Tika automatically detects the context key by checking if your class implements one of these known interfaces:
-
Parser,Detector,EncodingDetector -
MetadataFilter,Translator,Renderer -
DigesterFactory,ContentHandlerFactory,ContentHandlerDecoratorFactory -
MetadataWriteLimiterFactory,UnpackSelector,EmbeddedDocumentExtractor
@TikaComponent
public class CommonsDigesterFactory implements DigesterFactory {
// Context key automatically detected as DigesterFactory.class
}
Service Interface Categories
First-Class Service Interfaces
These are loaded via SPI and have dedicated index files:
| Interface | Index File |
|---|---|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Self-Configuring Components
SelfConfiguring is a marker interface with no methods: resolveAll skips such
components, leaving their JSON in ParseContext, and the component reads it where
it needs it. Parser extends SelfConfiguring, so every parser is self-configuring.
@TikaComponent
public class PDFParser implements Parser {
private final PDFParserConfig defaultConfig = new PDFParserConfig();
@Override
public void parse(TikaInputStream tis, ContentHandler handler, Metadata metadata,
ParseContext parseContext)
throws IOException, SAXException, TikaException {
PDFParserConfig config = ParseContextConfig.getConfig(
parseContext, "pdf-parser", PDFParserConfig.class, defaultConfig);
// Use config...
}
}
Benefits:
-
Per-request configuration via
ParseContext -
Lazy loading - config only parsed when needed
-
Merging with defaults handled automatically
ParseContext Serialization
ParseContext can be serialized to JSON for transmission (e.g., in FetchEmitTuple):
{
"parse-context": {
"pdf-parser": {
"ocr": {
"strategy": "AUTO"
},
"extractInlineImages": true
},
"commons-digester-factory": {
"digests": [{"algorithm": "SHA256"}]
}
}
}
All entries use this flat, friendly-named form and are resolved lazily: a config
is parsed into its component only when first needed (see resolveAll). There is
no separate "immediate" or "typed" form.
Security Model
The serialization system implements a security allowlist:
-
@TikaComponent Required: Only annotated classes are registered
-
Registry Lookup: Deserialization only instantiates registered classes
-
No Arbitrary Classes: Unknown class names cause errors, not instantiation
This prevents attacks where malicious JSON specifies dangerous classes for instantiation.
|
The allowlist governs which components may be instantiated from JSON. It does not restrict how an already-loaded component may be configured. Self-configuring components — which includes every That is why per-request configuration is gated separately by
|
{
"parse-context": {
"java.lang.Runtime": {}
}
}
That fails with "Unrecognized parse-context entry 'java.lang.Runtime'" — the class is not registered.
Untrusted (Wire) Input: Restricted Mode
Configuration files loaded at startup via TikaLoader are treated as trusted.
Per-request configuration arriving over the wire — tika-server request bodies
and pipes FetchEmitTuple`s — is deserialized in restricted mode
(`ParseContextDeserializer.readParseContext(node, true)), which adds a second,
fail-closed gate on top of the registry:
-
Only context-key types confined to shaping this request’s metadata or output may be instantiated from the wire:
MetadataFilter,ContentHandlerFactory,ContentHandlerDecoratorFactory,DigesterFactory,MetadataWriteLimiterFactory,UnpackSelector -
Types with exec/IO/network capability or control over which components run are blocked:
Parser,Detector,EncodingDetector,Renderer,Translator,EmbeddedDocumentExtractor -
The check is fail-closed: a newly added context-key interface is blocked until it is consciously allow-listed
-
The whole tree is scanned before any component is constructed
The allowlist/blocklist lives in ComponentNameResolver
(WIRE_INSTANTIABLE_CONTEXT_KEYS / WIRE_BLOCKED_CONTEXT_KEYS); an
exhaustiveness test asserts every context-key interface is classified as
exactly one of the two. Plain config DTOs (non-component keys) are never
blocked.
Framework Directives
Some JSON keys are consumed by the loading framework itself rather than by the component whose config object they appear in. When such a directive shares a JSON object with a component’s own properties, it carries a leading underscore to avoid namespace collisions with legitimate component config keys:
{
"parsers": [
{
"pdf-parser": {
"_mime-include": ["application/pdf"],
"_mime-exclude": ["application/pdf+fdf"],
"extractInlineImages": true
}
}
]
}
_mime-include/_mime-exclude are stripped before the component sees its
config and are applied by the framework as a MIME-filtering decorator around
the parser. New framework directives must follow the underscore convention.
Marker entries that have no component-config namespace of their own are the
exception: "exclude" on default-parser/default-detector/
default-encoding-detector needs no prefix because those markers carry only
framework keys.
Creating a Custom Component
Complete example of a custom metadata filter. fieldName is config-supplied, so it could
legitimately name a reserved tk: key; use setTrusted rather than set — filters run
inside the trusted bracket of MetadataFilter, the sanctioned route for a write keyed by
a name that isn’t a compile-time constant:
package com.example.tika;
import org.apache.tika.annotation.TikaComponent;
import org.apache.tika.metadata.Metadata;
import org.apache.tika.metadata.filter.MetadataFilterBase;
@TikaComponent
public class UpperCaseFilter extends MetadataFilterBase {
private String fieldName = "title";
public void setFieldName(String fieldName) {
this.fieldName = fieldName;
}
public String getFieldName() {
return fieldName;
}
@Override
protected void filter(Metadata metadata) {
String value = metadata.get(fieldName);
if (value != null) {
metadata.setTrusted(fieldName, value.toUpperCase());
}
}
}
Configure in JSON. Metadata filters are loaded via parse-context (they
extend the MetadataFilter abstract class — here via MetadataFilterBase,
whose per-Metadata hook suits context-free filters — which is a
ParseContext-keyed component):
{
"parse-context": {
"upper-case-filter": {"fieldName": "dc:title"}
}
}
Or with defaults (an empty config object applies no overrides):
{
"parse-context": {
"upper-case-filter": {}
}
}
Troubleshooting
"Unknown component name" Error
-
Ensure class has
@TikaComponentannotation -
Verify annotation processing ran during compilation
-
Check that
META-INF/tika/*.idxfile exists in JAR