Metadata

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

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

Baseline date: 2026-10-12. This page was written against Apache Tika 4.1.0 (the latest release on Maven Central on that date) with the 3.3.x differences noted, Java 21 and later for the examples (Tika 4.x needs Java 17), Spring Boot 4.x on Spring Framework 7 and the Docker image apache/tika:4.1.0. Metadata is the second output of every parse: the content handler receives the text, and the Metadata object receives everything else, from the title and author of a document to the media type Tika detected, the parsers that ran, the encoding it decided on and the warnings it raised. In Tika 4.x the key names changed more than anything else in the project, so this page spends most of its length on which key to read and what changed. It explains the Metadata class and typed Property keys, the standards Tika reuses (Dublin Core, XMP, Office, PDF, TIFF, Message), the tk: namespace and the complete 1.x to 3.x to 4.x mapping, the reserved-key write guard that makes metadata safer, the KeyPrefix write route, the filters and limiter that shape output, digests, and a table of the keys that matter for each Bookshelf intake file.

Related pages: Introduction & Architecture (where metadata sits in the pipeline), Getting Started with the Tika Facade (the facade’s metadata out-parameter), Media Types & Detection (the Content-Type family of keys) and Setup & Installation (dependencies).

All code on this page that prints output was compiled and run against Tika 4.1.0 (tika-core and tika-parsers-standard-package as a pom dependency) on Java 21. The sample files are small stand-ins written for this page: a one-heading manuscript.docx with a title and an author, a one-page catalogue.pdf (785 bytes), the same PDF renamed to renamed-invoice.png, a hand-written publisher-email.eml with a PDF attachment and a short reviews-es.html. The key names were additionally read from the tika-core 4.1.0, 3.3.2 and 1.28.5 source jars on Maven Central, and the migration table was cross-checked against the machine-readable metadata-migration-3x-4x.json bundled in tika-core 4.1.0. The section What was verified and what was not lists what could not be checked.

What metadata is, and what it is not

org.apache.tika.metadata.Metadata is a multi-valued map from a String key to one or more String values. It is the parameter every Parser.parse call fills, the second result of Tika.parse, and the per-document record of a RecursiveParserWrapper result. It carries three kinds of information:

Kind Examples Who asserts it

Document metadata

dc:title, dc:creator, dcterms:created, pdf:producer, message:from, tiff:Make

The file. These are claims the document makes about itself. Treat them as untrusted input.

Tika metadata

Content-Type, tk:parsed-by, tk:detected-encoding, tk:embedded-depth, tk:exception:warn

Tika. These describe what the parse did.

Caller hints

tk:resource-name, tk:content-type-hint, tk:content-type-override

The caller (or the transport, such as an HTTP header). They steer detection and parser choice.

The distinction is the whole point of the 4.x key redesign: in 4.x every key that Tika itself computes lives under the tk: prefix, and a file can no longer write into that namespace. A document that carries a custom property literally named like a Tika key cannot overwrite the value Tika computed. The rest of this page builds on that idea.

Metadata is not the document text. In the Parser API the text goes to the ContentHandler; only the RecursiveParserWrapper and Tika Pipes put the handler output into metadata, under the key tk:content. See Introduction & Architecture for the four pillars.

The Metadata class

Metadata is small. Its useful surface is:

Member Behaviour

names()

All keys currently present, as a String[] in no guaranteed order. Sort it for stable output.

get(String), get(Property)

The first value of a key, or null.

getValues(String), getValues(Property)

All values of a key, in insertion order, or an empty array.

isMultiValued(Property)

true when a key holds more than one value.

set(Property, String) and typed overloads (int, long, boolean, double, Date, Calendar)

Replace all values of a key. A null value removes the key.

add(Property, String) and typed overloads

Append a value. Only allowed for bag and sequence properties; on a single-valued property a second add throws PropertyTypeException.

getInt, getLong, getDouble, getBoolean, getDate, getIntValues, getLongValues

Typed reads that parse the stored string and return null when the value is absent or malformed.

set(String, String), add(String, String)

String-keyed writes. In 4.x they throw IllegalArgumentException for a reserved tk: or legacy X-TIKA: name. See The reserved-key write guard.

add(KeyPrefix, String, String)

The write route for key names that come from the document. See Document-derived names.

putAll(Metadata)

Copy another Metadata, preserving multiple values and reserved keys. This replaced the manual copy loop and setAll(Properties) (removed, no replacement).

remove(String), remove(Property), size()

Remove a key; count keys.

newInstance(ParseContext)

A new Metadata that applies the MetadataWriteLimiterFactory found in the context. New in 4.0.

new Metadata(MetadataWriteLimiter)

A Metadata with an explicit write limiter. New in 4.0.

All values are stored as strings; typed access is convenience over parsing. Every string value is stored well-formed: a lone UTF-16 surrogate is replaced by U+FFFD at write time (except for tk:content, which already went through a safe handler), so no serializer downstream meets one.

Metadata is not thread safe. Create one per parse and do not share it between threads while a parse runs.

Reading metadata after a parse

The simplest useful program parses a file and prints every key. This is the dump used to produce the output tables later on this page.

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

import org.apache.tika.io.TikaInputStream;
import org.apache.tika.metadata.Metadata;
import org.apache.tika.metadata.TikaCoreProperties;
import org.apache.tika.parser.AutoDetectParser;
import org.apache.tika.parser.ParseContext;
import org.apache.tika.sax.BodyContentHandler;

public class DumpMetadata {

    public static void main(String[] args) throws Exception {
        Path file = Path.of(args[0]);
        Metadata metadata = new Metadata();
        // A hint about the original name; detection can use it as a tie-breaker.
        metadata.set(TikaCoreProperties.RESOURCE_NAME_KEY, file.getFileName().toString());

        try (TikaInputStream tis = TikaInputStream.get(file, metadata)) {
            new AutoDetectParser().parse(tis, new BodyContentHandler(-1), metadata, new ParseContext());
        }

        String[] names = metadata.names();
        Arrays.sort(names);
        for (String name : names) {
            System.out.println(name + " = " + String.join(" | ", metadata.getValues(name)));
        }
    }
}

Run against manuscript.docx on Tika 4.1.0 (long values shortened here):

Content-Length = 1926
Content-Type = application/vnd.openxmlformats-officedocument.wordprocessingml.document
dc:creator = Ana Torres
dc:title = The Lighthouse Keeper
tk:content-type-magic-detected = application/vnd.openxmlformats-officedocument.wordprocessingml.document
tk:parsed-by = org.apache.tika.parser.DefaultParser | org.apache.tika.parser.microsoft.ooxml.OOXMLParser
tk:parsed-by-full-set = org.apache.tika.parser.DefaultParser | org.apache.tika.parser.microsoft.ooxml.OOXMLParser
tk:resource-name = manuscript.docx
zip:detector-zip-file-opened = true

Three observations follow from this output. The two document claims (dc:creator, dc:title) are bare Dublin Core names. Everything Tika asserted is under tk:. And Content-Type is the one Tika-computed key that is not under tk:: it is the routing key every parser and consumer already depends on, so it keeps its name.

Multiple values

Many keys are bags: a document may have several creators, subjects or parsers. get returns the first value and hides the others, which is a classic bug when indexing.

import java.util.Arrays;

import org.apache.tika.metadata.DublinCore;
import org.apache.tika.metadata.Metadata;

public class MultiValues {

    public static void main(String[] args) {
        Metadata metadata = new Metadata();
        metadata.add(DublinCore.CREATOR, "Ana Torres");
        metadata.add(DublinCore.CREATOR, "Marta Ruiz");

        System.out.println(metadata.get("dc:creator"));                       // Ana Torres
        System.out.println(Arrays.toString(metadata.getValues("dc:creator"))); // [Ana Torres, Marta Ruiz]
        System.out.println(metadata.isMultiValued(DublinCore.CREATOR));       // true
    }
}

On the Bookshelf manuscript.docx the tk:parsed-by key is multi-valued as well: it lists the DefaultParser wrapper first and the parser that did the work second. Read it with getValues. Any index mapping for it must be a multi-valued field.

Typed Property keys

Since Tika 2.0 a key is not just a string: it is usually a org.apache.tika.metadata.Property constant that knows its name, whether it is single- or multi-valued and what the value means. Property has two orthogonal characteristics.

Characteristic Values

PropertyType

SIMPLE (one value), BAG (unordered, repeatable), SEQ (ordered, repeatable), plus COMPOSITE (a property that writes to several keys at once, used for 1.x compatibility).

ValueType

BOOLEAN, OPEN_CHOICE, CLOSED_CHOICE, DATE, INTEGER, RATIONAL, REAL, TEXT, URI, PROPERTY.

internal or external

The XMP concept: an internal property is one the file format defines itself (for example PDF /Producer), an external one is user-supplied (for example a tag). It affects how Tika treats the value, not how you read it.

Public factories create properties in a parser module’s own namespace, for example Property.internalText("myformat:title"), Property.internalTextBag(...), Property.internalDate(...), Property.externalInteger(...) and Property.internalClosedChoice("tiff:Orientation", "1", ..., "8"). The PropertyType of a key matters at write time:

import org.apache.tika.metadata.Metadata;
import org.apache.tika.metadata.PropertyTypeException;
import org.apache.tika.metadata.TikaCoreProperties;

public class PropertyTypes {

    public static void main(String[] args) {
        Metadata metadata = new Metadata();

        // TikaCoreProperties.RESOURCE_NAME_KEY is a SIMPLE property: one value only.
        metadata.set(TikaCoreProperties.RESOURCE_NAME_KEY, "ok.docx");
        try {
            metadata.add(TikaCoreProperties.RESOURCE_NAME_KEY, "again.docx");
        } catch (PropertyTypeException e) {
            System.out.println("second add on a SIMPLE property: " + e.getClass().getSimpleName());
        }

        // Typed access: an INTEGER property is written and read as int.
        metadata.set(TikaCoreProperties.EMBEDDED_DEPTH, 2);
        System.out.println("depth = " + metadata.getInt(TikaCoreProperties.EMBEDDED_DEPTH)); // 2
    }
}

Both lines were run on Tika 4.1.0: the second add throws PropertyTypeException and the typed read returns 2. In 4.x some former String constants became SIMPLE properties instead of bags, so code that called add twice on them now fails loudly rather than silently accumulating. Use set for a genuinely single value, or a bag-typed key if it can repeat.

Dates are stored as ISO 8601 strings in UTC. metadata.set(TikaCoreProperties.CREATED, new Date(0)) stores 1970-01-01T00:00:00Z, and getDate parses it back. Parsers write dates through the Date and Calendar overloads, so you can rely on this shape for Office, PDF and e-mail dates, but a value can still be absent or unparsable for a damaged file.

The standards Tika reuses

Tika did not invent a metadata vocabulary. It maps each format’s native fields onto a handful of existing standards and exposes them as constants in tika-core (all in org.apache.tika.metadata). The first four rows are the ones to learn.

Constants class Key prefix What it covers

TikaCoreProperties

dc:, dcterms:, xmp:, tk:, Content-Type

The cross-format keys: TITLE (dc:title), CREATOR (dc:creator), CREATED (dcterms:created), MODIFIED (dcterms:modified), LANGUAGE, DESCRIPTION, SUBJECT, LATITUDE, LONGITUDE, ALTITUDE, and all the Tika-asserted tk: keys.

DublinCore

dc:, dcterms:

The Dublin Core elements: FORMAT, IDENTIFIER, CONTRIBUTOR, COVERAGE, CREATOR, DATE, DESCRIPTION, LANGUAGE, PUBLISHER, RELATION, RIGHTS, SOURCE, SUBJECT, TITLE, TYPE, plus dcterms:created and dcterms:modified. Most are bags.

HttpHeaders

bare names

Content-Type, Content-Encoding, Content-Language, Content-Length, Content-Location, Content-Disposition, Content-MD5, Location. These keep the HTTP header spelling because they are also used as HTTP headers.

Office

meta:

Word-processor and spreadsheet statistics: meta:author, meta:last-author, meta:creation-date, meta:save-date, meta:print-date, meta:page-count, meta:word-count, meta:character-count, meta:line-count, meta:paragraph-count, meta:table-count, meta:image-count, meta:object-count, meta:slide-count.

XMP, XMPDM, XMPPDF, XMPMM, XMPRights, XMPIdq

xmp:, xmpDM:, xmpTPg:, xmpMM:

Adobe XMP schemas. xmpDM: carries audio and video fields (xmpDM:artist, xmpDM:album, xmpDM:audioSampleRate and more). xmpTPg:NPages is the XMP page count.

PDF

pdf:, pdfa:, pdfaid:, pdf:docinfo:

PDF structure and Info-dictionary keys (pdf:producer, pdf:pdf-version, pdf:has-xfa, pdf:docinfo:title, …​).

TIFF, Photoshop, IPTC

tiff:, exif:, aux:, photoshop:, iptc:

Image keys: tiff:ImageWidth, tiff:ImageLength, tiff:Make, tiff:Model, tiff:Software, tiff:Orientation, exif:DateTimeOriginal, exif:ExposureTime, exif:FNumber, exif:IsoSpeedRatings, aux:Lens.

Message, MAPI

message:, multipart:, mapi:

E-mail: message:from, message:to, message:cc, message:bcc, message:from-email, message:from-name, multipart:subtype, multipart:boundary, and message:raw-header: plus the header name for every raw header.

AccessPermissions

access-permission:

PDF permission flags (access-permission:can-print, access-permission:extract-content, …​).

Zip, PST, Database, WordPerfect, QuattroPro, Rendering and more

zip:, pst:, database:, wordperfect: and more

Format-specific families, kebab-cased in 4.x.

The class list was taken from the org.apache.tika.metadata package of the 4.1.0 tika-core sources, and every key spelled out above was read from those constants. The set is far larger than this table; the official Adding a Metadata Key page notes that every key Tika can emit is a Property constant or, for names minted at run time, a KeyPrefix, and that a generated registry (three JSON files under tika-metadata-schema) lists all of them.

Metadata quality differs across formats

The same logical field is not available everywhere, and the value quality varies:

  • A .docx or .odt carries title and author as explicit properties, so dc:title and dc:creator are reliable when the author filled them in, and routinely empty when they did not.

  • A PDF has two sources, the Info dictionary and an XMP packet. In 4.x the normalized keys pdf:producer, dc:title and dc:creator take the XMP value when there is one and fall back to the Info dictionary otherwise; the individual sources stay visible as pdf:docinfo:* and xmp:pdf:*.

  • An HTML page gives <title> and <meta name=...> tags, which are written by whoever built the page and may be copied boilerplate.

  • An image gives EXIF, XMP and IPTC fields from the camera or editor, which are often missing or scrubbed.

  • A plain text file has none of these. It has the Content-Type, Content-Length and the encoding Tika detected.

Never use document metadata for a decision that needs trust (access control, ownership, billing). It is data the file author controls.

Source: Metadata Overview, Apache Tika wiki (written for the 2.x and 3.x generation, so key names there predate 4.x), DCMI Metadata Terms and Adobe XMP documentation.

The 4.x key design in one picture

A key falls into one of four families, and the family decides who may write it and how:

flowchart TD K["A metadata key"] --> Q1{"Asserted by Tika?"} Q1 -- "yes" --> TK["tk: namespace, kebab-case, written only through a Property or setTrusted"] Q1 -- "no" --> Q2{"Name known in advance?"} Q2 -- "yes" --> P["Curated Property constant such as dc:title, pdf:producer, tiff:Make"] Q2 -- "no, comes from the document" --> KP["KeyPrefix route such as html:author, custom:Shelf, message:raw-header:Message-ID"] TK --> G["String write throws IllegalArgumentException"] P --> W["Write with the Property, typed"] KP --> A["Append only, never throws, bounded"]

The next sections cover each family: first the tk: family and its migration, then the guard, then the KeyPrefix route.

The tk: namespace and the key changes in 4.x

What changed

Tika 4.0.0 consolidated every Tika-asserted metadata key under one prefix, tk:, normalized to kebab-case. It replaced the 3.x X-TIKA: prefix and several scattered prefixes (tika:, tika_pg:, rendering:, signature:, imagereader:) and a few bare keys (resourceName, hasSignature, embeddedResourceType). The official migration page calls this "a clean break: there is no automatic remapping". Code that reads these keys by string must change; code that uses the Java constants (TikaCoreProperties.TIKA_PARSED_BY and friends) keeps compiling, because the constants now resolve to the tk: names.

The Tika 4.1.x FAQ says tk:resource-name, but some 4.x material and older posts still mention X-TIKA:resourceName. That is not a conflict in the software. On 4.1.0, run for this page, the key Tika emits for the resource name is tk:resource-name, and the pre-4.0.0 X-TIKA: prefix is no longer emitted at all. It stays reserved during the 4.x line only so that a crafted file cannot forge it. If you see X-TIKA: in a 4.x answer, it describes 3.x or earlier.

Other prefix changes in the same release:

Category Change in 4.0.0

HTML custom metadata

Prefixed with html:.

Unrecognized image metadata

Prefixed with img:.

Curated MAPI metadata

Moved from meta:mapi- to mapi:.

QuattroPro metadata

Moved from wordperfect: (a collision with WordPerfect) to quattropro:.

Format-namespace casing

Names Tika coined are kebab-cased (pdf:hasXFA to pdf:has-xfa, access_permission:can_print to access-permission:can-print). Names that come from a standard, schema or the file (Dublin Core, XMP, EXIF, MAPI, ECMA-376 property names) keep their verbatim spelling.

The Content-Type key itself did not change, but its Java constant did: Metadata no longer implements the constant-holding interfaces (HttpHeaders, Message, TIFF and others), so Metadata.CONTENT_TYPE is gone and the code becomes metadata.get(HttpHeaders.CONTENT_TYPE), which returns a Property-backed key. The compiler message cannot find symbol: variable CONTENT_TYPE is the symptom.

The 1.x, 3.x and 4.x key-mapping table

The table maps the keys most Bookshelf code touches. 1.x is the generation documented by Tika in Action (Tika 1.0 in 2011; the key names below were checked in the 1.28.5 sources, the last 1.x release). 3.x was checked in 3.3.2. 4.x was checked in 4.1.0 and, for the highlighted rows, by running a parse. A dash means there was no such key in that generation, and "unverified" means the row could not be confirmed from source.

Meaning 1.x (book era) 3.x 4.x Notes

Media type

Content-Type (Metadata.CONTENT_TYPE)

Content-Type

Content-Type

Key unchanged. Java constant moved to HttpHeaders.CONTENT_TYPE in 4.x.

Resource name (file name hint)

resourceName (TikaMetadataKeys.RESOURCE_NAME_KEY, Metadata.RESOURCE_NAME_KEY)

resourceName (TikaCoreProperties.RESOURCE_NAME_KEY)

tk:resource-name

Run on 4.1.0: tk:resource-name. A parser may overwrite it: the .eml sample reported the e-mail subject plus .eml.

Parsers that ran

X-Parsed-By (written by CompositeParser)

X-TIKA:Parsed-By

tk:parsed-by

Multi-valued. The 1.28.5 source carries a comment about renaming it to the X-TIKA family; it was renamed by 3.x and again in 4.0.

Full set of parsers

not present in 1.28.5 tika-core

X-TIKA:Parsed-By-Full-Set

tk:parsed-by-full-set

Multi-valued.

Extracted text (recursive result)

X-TIKA:content

X-TIKA:content

tk:content

Present in 1.28.5 in AbstractRecursiveParserWrapperHandler.

Embedded document path

X-TIKA:embedded_resource_path

X-TIKA:embedded_resource_path

tk:embedded-resource-path

See Embedded Documents & Recursive Parsing for the whole containment model.

Embedded depth

X-TIKA:embedded_depth

X-TIKA:embedded_depth

tk:embedded-depth

Integer property.

Embedded relationship id

embeddedRelationshipId

embeddedRelationshipId

tk:embedded-relationship-id

Embedded resource type

embeddedResourceType

embeddedResourceType (per the official table)

tk:embedded-resource-type

Closed choice: INLINE, ATTACHMENT, MACRO, METADATA, FONT, THUMBNAIL, RENDERING, VERSION, ALTERNATE_FORMAT_CHUNK.

Embedded stream exception

X-TIKA:EXCEPTION:embedded_stream_exception

X-TIKA:EXCEPTION:embedded_stream_exception

tk:exception:embedded-stream-exception

All X-TIKA:EXCEPTION: keys became tk:exception: with kebab-case suffixes.

Container exception

unverified

X-TIKA:EXCEPTION:container_exception

tk:exception:container-exception

Write limit reached

unverified

X-TIKA:EXCEPTION:write_limit_reached

tk:exception:write-limit-reached

Boolean. Signals a truncated extraction.

Truncated metadata warning

unverified

X-TIKA:WARN:truncated_metadata

tk:warn:truncated-metadata

Set when the metadata limiter dropped data.

Detected encoding

-

X-TIKA:detectedEncoding

tk:detected-encoding

Run on 4.1.0 for reviews-es.html: UTF-8.

Encoding detector

-

X-TIKA:encodingDetector

tk:encoding-detector

Run on 4.1.0: HtmlEncodingDetector. tk:encoding-detection-trace is new in 4.0.

Detected language

-

X-TIKA:detected_language

tk:detected-language

Plus tk:detected-language-confidence and tk:detected-language-confidence-raw.

Digest

unverified

X-TIKA:digest:SHA256

tk:digest:SHA-256

The algorithm is renamed to its JCA spelling in 4.x; encoding suffix kept (tk:digest:MD5:BASE32).

Encrypted flag

-

X-TIKA:encrypted

tk:encrypted

Version count and number

-

X-TIKA:versionCount, X-TIKA:versionNumber

tk:version-count, tk:version-number

Run on 4.1.0: tk:version-count = 0 for the sample PDF.

Page number (paged text)

-

tika_pg:page_number

tk:page:number

tk:page:numbers is new in 4.0.

Signature fields

-

hasSignature, signature:name, signature:date and more

tk:has-signature, tk:signature:name, tk:signature:date and more

Title

title (Metadata.TITLE) and dc:title

dc:title

dc:title

In 1.x TikaCoreProperties.TITLE was a composite that wrote both. Unchanged since 2.0.

Author

Author, creator, meta:author and dc:creator

dc:creator (constant), parsers may add meta:author

dc:creator

In 1.x TikaCoreProperties.CREATOR was a composite writing four keys. Prefer dc:creator.

Last author

Last-Author (MSOffice.LAST_AUTHOR, deprecated) and meta:last-author

meta:last-author

meta:last-author

Creation date

Creation-Date (MSOffice, deprecated) and meta:creation-date

meta:creation-date, dcterms:created

dcterms:created, meta:creation-date

Run on 4.1.0, the .eml Date header produced dcterms:created.

Page count

Page-Count (MSOffice, deprecated) and meta:page-count

meta:page-count, xmpTPg:NPages

meta:page-count, xmpTPg:NPages

Run on 4.1.0, the PDF reported xmpTPg:NPages = 1.

E-mail sender

Message-From

Message-From

message:from

Also message:from-email and message:from-name. Message-To, Message-Cc, Message-Bcc became message:to, message:cc, message:bcc.

E-mail raw header

unverified

Message:Raw-Header:Name

message:raw-header:Name

Run on 4.1.0: message:raw-header:Message-ID. Prefix rule in LegacyKeyMigrationFilter.

PDF permission

unverified

access_permission:can_print

access-permission:can-print

Run on 4.1.0 for catalogue.pdf.

PDF XFA flag

unverified

pdf:hasXFA

pdf:has-xfa

Run on 4.1.0 for catalogue.pdf.

PDF version

unverified

pdf:PDFVersion

pdf:pdf-version

Run on 4.1.0: 1.4.

HTML custom <meta>

bare names such as author

bare names

html:author style prefix

The sample reviews-es.html shows dc:creator, dc:subject and meta:keyword (the recognized names); only unrecognized <meta> names get the html: prefix.

Unrecognized image keys

bare names

bare names

img:name

Office custom property

custom:Name (see note)

meta:some-property (per the official table)

office:some-property (per the official table)

See the unresolved note below the table.

Notes on the table:

  • The "1.x" column lists what 1.28.5 source shows, not what Tika in Action printed in 2011, which targeted 1.0 and predates some of these constants. Treat the book’s Metadata.AUTHOR style listings as outdated idioms: those constants are deprecated in 1.x, and removed or moved in 3.x and 4.x.

  • Rows marked from "the official table" come from the Tika 4.1.x Metadata Changes in Tika 4.x page and were not reproduced by a run.

  • Unresolved: for an Office custom (user-defined) property the 4.1.x migration page lists meta:some-property to office:some-property, but Office.USER_DEFINED_METADATA_NAME_PREFIX in the 4.1.0 tika-core sources is custom: and metadata.add(Office.USER_DEFINED, "Shelf", "A12") produced custom:Shelf when run. The OOXML parser module may use its own prefix; this could not be settled without a document that has custom properties, so check your own files before relying on either spelling.

  • The bundled metadata-migration-3x-4x.json (253 rows in 4.1.0) is broader than the tables on the official page. For example it maps the Message-* family, which the page tables do not list. It is the authoritative key-for-key list.

New in 4.x with no 3.x predecessor

These keys have nothing to migrate from. Useful ones for intake code:

Key Meaning

tk:chunks

Chunks of the extracted content (4.0), used by embedding and RAG pipelines.

tk:content-handler-type

The handler type enum name (replaces the dropped 3.x X-TIKA:content_handler, which held a class name; the new value shape differs).

tk:content-type-magic-detected

The type detected from magic bytes, before hints. Run on 4.1.0: present on every sample.

tk:decoded-charset, tk:encoding-detection-trace

The charset used to decode text, and the detectors' decisions, for example HtmlEncodingDetector followed by the other detectors.

tk:detection-content-length, tk:truncated-content-for-detection

How much content detection read and whether it was cut short.

tk:resource-name-extension-inferred

Whether the resource name’s extension was inferred rather than supplied.

tk:exception:embedded-depth-limit-reached

A depth limit stopped recursion.

tk:inference-released (4.1)

Tika released inference resources for the document.

tk:rendering:failed-page (4.1)

1-based pages a rendering engine could not draw.

zip:* family

Integrity checks for ZIP containers, for example zip:detector-zip-file-opened, zip:integrity-check-result, zip:compression-method.

vlm:prompt-tokens, vlm:completion-tokens

Token usage of the new VLM parsers.

Dropped, not renamed

Some 3.x keys have no 4.x equivalent. The migration JSON marks them DROPPED, for example X-TIKA:content_handler, pst:folderPath (the Outlook parser now folds the folder into tk:internal-path, a superset) and cp:subject. A script that migrates an index must handle both the renamed and the dropped rows, plus the prefix families (NER_* to ner:*, iworks:* to iwork:*, MDB_PROP:* to mdb-prop:*, Message:Raw-Header:* to message:raw-header:*, digests, and the open-vocabulary GDAL, HDF, NetCDF and ISO 19115 families).

Source: Metadata Changes in Tika 4.x, Apache Tika 4.1.x and metadata-migration-3x-4x.json on GitHub (the file bundled in tika-core; the path was read from the 4.1.0 sources jar, the GitHub URL is the project’s repository layout and was not fetched).

Restoring 3.x key names with LegacyKeyMigrationFilter

If downstream consumers (an index schema, a dashboard) cannot move to tk: names yet, Tika does not ask you to fork it. The opt-in LegacyKeyMigrationFilter metadata filter rewrites keys at the emit edge using the same migration table. It works in either direction and is configured in the parse-context section of tika-config.json:

{
  "parse-context": {
    "legacy-key-migration-filter": {}
  }
}

The default direction is V4_TO_V3: Tika’s 4.x output is rewritten into 3.x spellings. For input that is 3.x shaped and must be read with 4.x names, set direction:

{
  "parse-context": {
    "legacy-key-migration-filter": { "direction": "V3_TO_V4" }
  }
}

An optional table overrides the bundled table with a custom one in the same row shape ({"v3": "...", "v4": "..."}, with "v4": "DROPPED" for a key that has no successor). The filter is also usable programmatically, as in this run against the metadata of catalogue.pdf:

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

import org.apache.tika.metadata.Metadata;
import org.apache.tika.metadata.filter.LegacyKeyMigrationFilter;

public class LegacyKeys {

    public static void main(String[] args) throws Exception {
        // 3.x shaped input, mapped forward to 4.x names
        Metadata threeX = new Metadata();
        threeX.setTrusted("X-TIKA:Parsed-By", "org.apache.tika.parser.DefaultParser");
        threeX.setTrusted("resourceName", "catalogue.pdf");
        threeX.setTrusted("pdf:hasXFA", "false");

        LegacyKeyMigrationFilter.Config config = new LegacyKeyMigrationFilter.Config();
        config.direction = LegacyKeyMigrationFilter.Direction.V3_TO_V4;
        List<Metadata> list = new ArrayList<>(List.of(threeX));
        new LegacyKeyMigrationFilter(config).filter(list);

        String[] names = threeX.names();
        Arrays.sort(names);
        System.out.println(Arrays.toString(names));
    }
}

Output on Tika 4.1.0:

[pdf:has-xfa, tk:parsed-by, tk:resource-name]

The reverse direction (the default) turned the 4.x metadata of catalogue.pdf into keys such as X-TIKA:Parsed-By, X-TIKA:Parsed-By-Full-Set, X-TIKA:digest:SHA256, X-TIKA:versionCount and access_permission:can_print. The filter changes the metadata in place.

There is one limit worth knowing: families whose 3.x keys were completely bare (HDF, NetCDF, residual GDAL keys, ISA-Tab, QuickTime, audio SPI keys) are restored only when going 4.x to 3.x, because on ingest a bare name does not identify its family. The filter passes them through when migrating forward.

The reserved-key write guard

In 3.x a call such as metadata.set("X-TIKA:Parsed-By", value) simply succeeded, so a custom property in a hostile document, written by a parser through a string key, could replace a value Tika had computed. In 4.0 the string-keyed writes set(String, String) and add(String, String) throw IllegalArgumentException for any reserved name: a tk: key, and, during the 4.x line, the legacy X-TIKA: prefix.

import org.apache.tika.metadata.Metadata;
import org.apache.tika.metadata.TikaCoreProperties;

public class ReservedKeys {

    public static void main(String[] args) {
        Metadata metadata = new Metadata();

        try {
            metadata.set("tk:resource-name", "evil.docx");
        } catch (IllegalArgumentException e) {
            System.out.println("rejected: " + e.getMessage());
        }

        // The supported route is the Property constant.
        metadata.set(TikaCoreProperties.RESOURCE_NAME_KEY, "ok.docx");
        System.out.println(metadata.get("tk:resource-name")); // ok.docx
    }
}

On Tika 4.1.0 the first call fails with the message "Writing reserved key 'tk:resource-name' via the String API is not allowed", which names the three legitimate alternatives: the Property constant, add(KeyPrefix, ...) for document-derived names, and putAll or setTrusted for intentional reconstruction. The same message appears for X-TIKA:Parsed-By. A null value (a removal) is always allowed.

Need Do this

Set a key Tika owns

Use its Property constant (TikaCoreProperties.RESOURCE_NAME_KEY).

Copy metadata between objects

target.putAll(source). A loop for (String n : src.names()) dest.set(n, src.get(n)) collapses multi-valued keys and throws on every tk: key that every parse result contains.

Rebuild metadata from JSON or a database (metadata filters, deserializers)

setTrusted or addTrusted, which bypass the guard. Use them only in code you control.

Write names that come from the document

add(KeyPrefix, name, value).

Three further consequences matter in production:

  • The guard throws an IllegalArgumentException, a RuntimeException, not a TikaException. A harness that narrowly catches TikaException around a parse will see an unmigrated parser hitting a crafted file as a new uncaught failure.

  • Property's public factories reject a reserved name at construction. Because those calls usually initialize static final constants, the failure surfaces as ExceptionInInitializerError (then NoClassDefFoundError on later use) far from the declaration; read the cause for the reserved name.

  • /pipes and /async requests may still carry their own tk:-named user metadata, and it lands in that request’s output; this is deliberate and bounded to the requester’s own request.

Document-derived names and KeyPrefix

When key names come from the document (HTML <meta name=...> tags, custom Office properties, CSV headers, e-mail headers) they cannot be constants. A parser declares a KeyPrefix once, as a static final field, and writes through Metadata#add(KeyPrefix, String, String):

import org.apache.tika.metadata.KeyPrefix;
import org.apache.tika.metadata.Metadata;

public class ShelfHeaders {

    // Declared once per parser module: never per parse, never from document text.
    static final KeyPrefix SHELF = KeyPrefix.file("shelf:", "the intake sheet's own header fields");

    static void record(Metadata metadata, String nameFromDocument, String value) {
        metadata.add(SHELF, nameFromDocument, value);
    }
}

The route has these guarantees: it is append-only (a repeated name accumulates values), it never throws on hostile input (a blank name, an over-length name, or a flood of distinct names is skipped with a warning), and source-typed dates have an Instant overload that stores canonical ISO 8601. Use KeyPrefix.file(...) for names read from the document and KeyPrefix.tool(...) for names coined by an external tool. The same route is what produced these keys in runs on Tika 4.1.0: message:raw-header:Message-ID and message:raw-header:MIME-Version for the e-mail, and custom:Shelf when the Office.USER_DEFINED prefix was used directly.

A parser module author should know that keys with a known, bounded vocabulary belong in curated Property constants, and that adding a key to TikaCoreProperties requires the build’s registry regeneration (tika-metadata-schema/regen.sh). That is contributor-level detail; the Extending Tika page covers custom parsers.

Metadata filters

A metadata filter rewrites metadata after parsing and before it is emitted or serialized: the last chance to drop a field, rename it, normalize it or add a derived one. Filters run in tika-server, Tika Pipes and (for its -J output only, in 4.1.0) tika-app, on the whole list of metadata objects (the container document first, then each embedded document).

metadata-filters is a top-level array in tika-config.json. Filters run in the listed order, each seeing the previous output. An element is a filter name (default settings) or an object keyed by the name with its configuration. There is no default filter and no implicit filtering: omit the section and the metadata is emitted unchanged.

Name What it does Options (default)

include-field-metadata-filter

Keeps only the named fields.

include, a list of field names (empty).

exclude-field-metadata-filter

Drops the named fields.

exclude, a list (empty).

field-name-mapping-filter

Renames fields; with excludeUnmapped it also acts as an allow-list.

mappings (old name to new name), excludeUnmapped (true).

remove-by-mime-metadata-filter

Removes a document’s whole Metadata from the list if its Content-Type base type matches.

mimes, base types without parameters.

clear-by-attachment-type-metadata-filter

Empties (but keeps) the metadata of an embedded document whose tk:embedded-resource-type matches.

types, from INLINE, ATTACHMENT, MACRO, METADATA, FONT, THUMBNAIL, RENDERING, VERSION, ALTERNATE_FORMAT_CHUNK.

date-normalizing-metadata-filter

Rewrites DATE-typed fields lacking a timezone to ISO 8601 UTC.

defaultTimeZone (UTC).

capture-group-metadata-filter

Regex over the first value of sourceField; capture group 1 goes to targetField.

regex, sourceField, targetField, all required.

geo-point-metadata-filter

Writes "lat,lon" to one field, the shape Elasticsearch and Solr expect.

geoPointFieldName (location).

legacy-key-migration-filter

Rewrites tk: keys to 3.x spellings, or forward.

direction (V4_TO_V3), table.

no-op-filter

Does nothing.

none

Optional modules add filters when on the classpath: the language detectors (charsoup-metadata-filter, optimaize-metadata-filter, open-nlp-metadata-filter), tika-eval-metadata-filter for extraction-quality scores, and the deprecated embedding filters of tika-inference (deprecated in 4.1.0, removed in 4.2.0 according to the 4.1.x documentation).

In 4.1.0, tika-eval-metadata-filter cannot be named in tika-config.json: the load fails with "Component …​ is not registered" because the tika-eval-core jar does not register it. Add it from Java instead, as tika-eval & Extraction Quality shows.

Emit only the fields your index needs

The Bookshelf search schema only needs a handful of fields. An allow-list keeps the index small and avoids storing document-controlled keys nobody queries:

{
  "metadata-filters": [
    {
      "include-field-metadata-filter": {
        "include": ["tk:content", "Content-Type", "dc:title", "dc:creator"]
      }
    }
  ]
}

The same filter was exercised in code on Tika 4.1.0: a metadata object holding tk:resource-name, Content-Type and dc:title, filtered with an include set of Content-Type and dc:title, kept exactly those two.

Rename fields for a downstream schema

excludeUnmapped defaults to true, so this keeps only the four mapped fields under their new names:

{
  "metadata-filters": [
    {
      "field-name-mapping-filter": {
        "excludeUnmapped": true,
        "mappings": {
          "tk:content": "content",
          "Content-Type": "mime",
          "dc:title": "title",
          "dc:creator": "author"
        }
      }
    }
  ]
}

Strip media-type parameters, and drop embedded images

Content-Type often arrives as text/plain; charset=UTF-8. To index only the base type, copy the part before the semicolon back onto the same field:

{
  "metadata-filters": [
    {
      "capture-group-metadata-filter": {
        "sourceField": "Content-Type",
        "targetField": "Content-Type",
        "regex": "\\A([^;]+)"
      }
    }
  ]
}

To keep inline images out of an .docx result without losing the text, blank their metadata, or remove those documents from the list altogether by media type:

{
  "metadata-filters": [
    { "clear-by-attachment-type-metadata-filter": { "types": ["INLINE"] } },
    { "remove-by-mime-metadata-filter": { "mimes": ["image/jpeg", "image/png"] } }
  ]
}

Ordering and where filters apply

Composition is literal: filter n sees the output of filter n-1. An allow-list therefore runs last, or it removes the fields a later filter wanted (an include-field-metadata-filter before geo-point-metadata-filter drops the latitude and longitude unless they are included). A rename also runs last, or later filters must use the new names.

Filters apply in tika-app before writing -J output (in a 4.1.0 run, -m and -j ignored the chain, as Configuration shows), in tika-server's /tika, /rmeta, /meta and /unpack inside the forked JVM before the response is written, and in Tika Pipes at the emit edge. A parse-context may carry a per-request metadata-filters list that replaces the configured one for that request. In the CONTENT_ONLY Pipes parse mode, Tika applies an include-field-metadata-filter for tk:content and tk:exception:container-exception by default when you configured none, and the top-level chain is not consulted; if you supply a filter in that mode you are responsible for keeping tk:content.

The metadata limiter

A hostile or just unlucky file can carry a huge amount of metadata: a million custom properties, a 50 MB value. The metadata limiter bounds the size of each parse’s metadata at write time, before memory is spent. It is the StandardMetadataLimiterFactory, configured under parse-context as standard-metadata-limiter-factory:

{
  "parse-context": {
    "standard-metadata-limiter-factory": {
      "maxTotalBytes": 1048576,
      "maxFieldSize": 102400,
      "maxKeySize": 1024,
      "maxValuesPerField": 100
    }
  }
}

The defaults, read from the 4.1.0 source, are:

Setting Default Meaning

maxKeySize

1,024

Maximum key size in UTF-16 bytes; longer keys are truncated.

maxFieldSize

102,400 (100 KiB)

Maximum size of one value; longer values are truncated.

maxTotalBytes

10,485,760 (10 MiB)

Estimated total size of all keys and values in the object.

maxValuesPerField

10

Maximum number of values for a multi-valued key.

includeFields, excludeFields

empty

Allow-list or deny-list of key names. Empty include means all fields.

includeEmpty

false

Whether empty values are stored.

Some keys bypass the limiter so that Tika’s own machinery keeps working: Content-Type, Content-Length, Content-Encoding, Content-Disposition, tk:content, tk:resource-name, the tk:content-type-* hints and two PDF access-permission keys always get through, with a minimum field size of 300 bytes. When data is dropped, Tika sets tk:warn:truncated-metadata to true; check it before trusting that a record is complete.

To apply the limiter in code, put the factory on the ParseContext and create the Metadata with Metadata.newInstance(context):

import java.nio.file.Path;

import org.apache.tika.io.TikaInputStream;
import org.apache.tika.metadata.Metadata;
import org.apache.tika.metadata.writelimiter.MetadataWriteLimiterFactory;
import org.apache.tika.metadata.writelimiter.StandardMetadataLimiterFactory;
import org.apache.tika.parser.AutoDetectParser;
import org.apache.tika.parser.ParseContext;
import org.apache.tika.sax.BodyContentHandler;

public class LimitedMetadata {

    public static void main(String[] args) throws Exception {
        StandardMetadataLimiterFactory limiter = new StandardMetadataLimiterFactory();
        limiter.setMaxValuesPerField(2);
        limiter.setMaxFieldSize(10 * 1024);

        ParseContext context = new ParseContext();
        context.set(MetadataWriteLimiterFactory.class, limiter);

        Metadata metadata = Metadata.newInstance(context);
        try (TikaInputStream tis = TikaInputStream.get(Path.of("catalogue.pdf"))) {
            new AutoDetectParser().parse(tis, new BodyContentHandler(-1), metadata, context);
        }
        System.out.println(metadata.get("tk:warn:truncated-metadata")); // null when nothing was dropped
    }
}

This compiled and ran on Tika 4.1.0 against the sample PDF. With a maximum of two values the two-valued tk:parsed-by fit, so nothing was dropped and the warning key was absent. Parsers that create metadata for embedded documents should use Metadata.newInstance(context) rather than new Metadata(), so the same limits apply to children.

Digests

Tika can hash documents while parsing, for deduplication, integrity checks and forensics. Digests land in metadata under tk:digest:<ALGORITHM> (hex encoding, the default) or tk:digest:<ALGORITHM>:<ENCODING> for other encodings, for example tk:digest:SHA-256:BASE32. The key carries the algorithm’s JCA name (SHA-256, SHA3-512), while the configuration names it by the enum spelling (SHA256, SHA3_512). Two factories are configured in parse-context:

Factory Config key Algorithms

CommonsDigesterFactory

commons-digester-factory

MD2, MD5, SHA1, SHA256, SHA384, SHA512 (Apache Commons Codec).

BouncyCastleDigesterFactory

bouncy-castle-digester-factory

All of the above plus SHA3_256, SHA3_384, SHA3_512 (BouncyCastle provider, extra dependency).

Encodings are HEX (default, lowercase), BASE32 and BASE64, both RFC 4648. In 3.x the key was X-TIKA:digest:SHA256, so a digest-keyed index needs the LegacyKeyMigrationFilter or a migration.

{
  "parse-context": {
    "commons-digester-factory": {
      "digests": [
        { "algorithm": "MD5", "encoding": "BASE32" },
        { "algorithm": "SHA256" }
      ],
      "skipContainerDocumentDigest": false
    }
  }
}

skipContainerDocumentDigest set to true digests only the embedded documents of a container (say, the PDFs inside a ZIP) and not the container. Programmatically:

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

import org.apache.tika.digest.DigestDef;
import org.apache.tika.digest.DigesterFactory;
import org.apache.tika.io.TikaInputStream;
import org.apache.tika.metadata.Metadata;
import org.apache.tika.parser.AutoDetectParser;
import org.apache.tika.parser.ParseContext;
import org.apache.tika.parser.digestutils.CommonsDigesterFactory;
import org.apache.tika.sax.BodyContentHandler;

public class DigestsInMetadata {

    public static void main(String[] args) throws Exception {
        CommonsDigesterFactory factory = new CommonsDigesterFactory();
        factory.setDigests(Arrays.asList(
                new DigestDef(DigestDef.Algorithm.SHA256),
                new DigestDef(DigestDef.Algorithm.MD5, DigestDef.Encoding.BASE32)));

        ParseContext context = new ParseContext();
        context.set(DigesterFactory.class, factory);

        Metadata metadata = new Metadata();
        try (TikaInputStream tis = TikaInputStream.get(Path.of("catalogue.pdf"))) {
            new AutoDetectParser().parse(tis, new BodyContentHandler(-1), metadata, context);
        }
        System.out.println("tk:digest:SHA-256 = " + metadata.get("tk:digest:SHA-256"));
        System.out.println("tk:digest:MD5:BASE32 = " + metadata.get("tk:digest:MD5:BASE32"));
    }
}

Run on Tika 4.1.0 against the 785-byte catalogue.pdf, the keys were tk:digest:SHA-256 (a 64-character hex value) and tk:digest:MD5:BASE32 (a base32 value padded with =). The digest needs the bytes: Tika reads the stream, spooling it to a temporary file when the digester requires it, so enabling digests adds I/O. On the command line, tika-app's --digest=<algorithm> covers the Commons algorithms only; for SHA3 use a JSON configuration with the BouncyCastle factory.

Keys to read for each Bookshelf file

The table lists the keys worth storing for each file of the intake scenario. Rows marked run were produced by a parse on Tika 4.1.0 of the stand-in sample; rows marked source come from constants in the 4.1.0 tika-core source and were not observed with a real file (the book-cover.jpg, scanned-loan-form.pdf and novel.epub samples were not run for this page).

File Keys worth keeping Basis

manuscript.docx

Content-Type, dc:title, dc:creator, tk:parsed-by, tk:resource-name. For a real manuscript also dcterms:created, dcterms:modified, meta:last-author, meta:page-count, meta:word-count.

run (first five), source (the rest)

catalogue.pdf

Content-Type, dc:title, dc:creator, pdf:producer, pdf:pdf-version, xmpTPg:NPages, pdf:has-xfa, pdf:encrypted, pdf:ocr-page-count, access-permission:extract-content, pdf:has-marked-content

run

scanned-loan-form.pdf

pdf:ocr-page-count, pdf:chars-per-page, pdf:unmapped-unicode-chars-per-page (low text per page signals a scan), plus the tess: keys when Tesseract runs.

source and run (the pdf: keys appear on the text PDF)

book-cover.jpg

tiff:ImageWidth, tiff:ImageLength, tiff:Make, tiff:Model, exif:DateTimeOriginal, dc:title, xmp:CreatorTool when present.

source

publisher-email.eml

message:from, message:from-email, message:to, message:cc, dc:title (the subject), dcterms:created, message:raw-header:Message-ID, multipart:subtype

run

novel.epub

dc:title, dc:creator, dc:language, dc:publisher, dc:identifier

source (Dublin Core constants; EPUB keys not run)

reviews-es.html

Content-Type, Content-Language, Content-Encoding, tk:detected-encoding, tk:encoding-detector, dc:title, dc:creator, dc:subject

run

reseñas-fr.txt and legacy-catalogue.txt

Content-Type, Content-Encoding, tk:detected-encoding, tk:decoded-charset, tk:encoding-detection-trace

source (txt files not run)

renamed-invoice.png

Content-Type (reports application/pdf), tk:resource-name (the lying name), tk:content-type-magic-detected

run

The last row is the point of the example on the Media Types & Detection page. The same PDF, called renamed-invoice.png, produced the Content-Type application/pdf, kept tk:resource-name = renamed-invoice.png and added tk:content-type-magic-detected = application/pdf. An intake service should compare what the file name claims with Content-Type and reject or quarantine a mismatch. See Media Types & Detection for validating uploads by content.

Pitfalls

  • Reading with get when the key is a bag. dc:creator, dc:subject and tk:parsed-by can hold several values; use getValues.

  • Keeping the 3.x names. Code that does metadata.get("X-TIKA:Parsed-By") or metadata.get("resourceName") returns null on 4.x without an error. Grep your code and index schemas for X-TIKA, resourceName, access_permission, Message-From, pdf:hasXFA, or enable LegacyKeyMigrationFilter.

  • Trusting the file’s claims. dc:creator is whatever the author typed. Do not use it for authorization.

  • Writing tk: keys by string. It throws. Use Property, putAll or, in trusted code, setTrusted.

  • Narrow catch (TikaException e). The guard and PropertyTypeException are `IllegalArgumentException`s.

  • Assuming Content-Type is a bare media type. It can carry parameters (text/html; charset=UTF-8); use MediaType.parse or the capture-group filter above.

  • Unbounded metadata at scale. Without a limiter, a crafted file can carry megabytes of properties. Configure standard-metadata-limiter-factory for untrusted input and check tk:warn:truncated-metadata.

  • Copy loops. Use putAll.

  • Assuming tk:resource-name is the upload name. Some parsers overwrite it (the e-mail parser set it from the subject). Keep your own copy of the original file name.

What was verified and what was not

Verified for this page:

  • The tk: key names for tk:parsed-by, tk:parsed-by-full-set, tk:resource-name, tk:content-type-magic-detected, tk:detected-encoding, tk:encoding-detector, tk:encoding-detection-trace, tk:version-count, the digest keys and the reserved-key guard behaviour, by running parses on Tika 4.1.0.

  • The names of the tk: constants in TikaCoreProperties, the Metadata method set, the limiter defaults and the always-allowed field set, by reading the 4.1.0 tika-core sources.

  • The 1.x and 3.x key names in the mapping table for the rows described as checked, by reading the 1.28.5 and 3.3.2 tika-core source jars.

  • The existence and behaviour of LegacyKeyMigrationFilter in both directions, by running it.

Not verified:

  • The tika-server JSON output was not inspected on this page. The tk: names in tika-app output (including tk:content in -J) are confirmed by runs on the Embedded Documents & Recursive Parsing and Configuration pages.

  • The 3.x embeddedResourceType, the 1.x forms of the exception and digest keys (marked "unverified" in the table), and all "per the official table" rows, which come from the Tika 4.1.x migration page only.

  • The Office custom-property prefix (custom: in source versus office: in the migration page), as explained above.

  • The keys produced by real JPEG, EPUB, scanned PDF and encoded text samples (marked source in the table).

  • The exact Tika in Action listings: the 1.x column reflects 1.28.5, not the 1.0 sources the book was written for.

  • The GitHub URL of the migration JSON in the Source line, which follows the repository layout and was not fetched.

  • The Tika 4.1.x documentation says its index page was generated with AI assistance and needs human review, so the pages cited here were cross-checked against sources and runs wherever the table above says so.