What’s Changed & Migration

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-13. This page was written against Apache Tika 4.1.0 (released 29 September 2026, still the latest release on the baseline date) and the 3.x maintenance line (3.3.2, released 16 July 2026), Java 21 for the examples (Tika 4.x needs Java 17 or later, 3.x needs Java 11), Spring Boot 4.x on Spring Framework 7 and the Docker image apache/tika:4.1.0. Every other page of this section ends with a short "1.x to 4.x" table for its own topic; this page collects them into one place. It gives the release timeline and the support window of every line, walks through what each major version changed, lists the vulnerabilities that make an upgrade urgent, and maps the idioms of Tika in Action (written for Tika 1.0 in 2011) to the code that replaces them. It ends with a complete migration of the Bookshelf intake program and an ordered checklist for moving a 3.x application to 4.1.

The page is a hub, not a replacement for the topic pages. Each row of each table links to the page that explains the change in depth: Configuration for tika-config.json, Metadata for the tk: keys, Tika Server (REST API) for the server, Tika App (Command Line & GUI) for the command line and Robustness, Limits & Timeouts for the timeout model.

Version timeline

timeline title Apache Tika release lines Nov 2011 : 1.0, the release Tika in Action describes Jul 2021 : 2.0.0 splits tika-parsers into modules and adds Tika Pipes Sep 2022 : 1.28.5 is the last 1.x release : 1.x end of life on 30 September Oct 2024 : 3.0.0 requires Java 11 and moves to PDFBox 3 Apr 2025 : 2.x end of support May 2025 : 2.9.4, the final 2.x release Mar 2026 : 3.3.0 moves to poi-ooxml-full May 2026 : 4.0.0-alpha-1 Jul 2026 : 4.0.0-beta-1 : 3.3.2 makes SAX OOXML parsing the default Aug 2026 : 4.0.0, first stable 4.x release, Java 17 Sep 2026 : 4.1.0, text recognizers, presets, env interpolation Jan 2027 : 3.x end of support by the wiki rule Mar 2027 : 3.x end of support by the 4.1.x roadmap page

The dates are the announcement dates of the Apache Tika news page, which can be a few days after the artifacts reached Maven Central (the tika-core 3.0.0 POM is dated 16 October 2024, the announcement 19 October 2024). The two 3.x end-of-support entries are not a typo: the sources disagree, as explained below.

Release Announced Java What it is remembered for

1.0

7 November 2011

5

The first 1.x release, the one Tika in Action was written for. It removed the pre-1.0 deprecated APIs and the JDK 1.4 build.

1.28.5

14 September 2022

8

The last 1.x release, a security fix.

2.0.0

19 July 2021

8

Parser modules split out of tika-parsers, Tika Pipes, metadata clean-up, tika-server-standard. (2.0.0-ALPHA was announced on 16 January 2021.)

2.9.4

5 May 2025

8

The final 2.x release; the announcement marked 2.x and Java 8 support as end of life.

3.0.0

19 October 2024

11

Java 11, jakarta namespaces, PDFBox 3, JSoup-based HTML parsing, tika-grpc (first in 3.0.0-BETA2).

3.3.0

23 March 2026

11

poi-ooxml-full instead of poi-ooxml-lite, better ZIP parsing.

3.3.2

16 July 2026

11

SAX-based OOXML parsers ported from 4.x and made the default; /pipes, /async and /status need enableUnsecureFeatures=true; fix for CVE-2026-66755.

4.0.0-alpha-1

8 May 2026

17

The first 4.x preview: a large refactoring with breaking changes.

4.0.0-beta-1

3 July 2026

17

Markdown default, zip distributions, pf4j pipes plugins; fixes CVE-2026-66755 and CVE-2026-66756 for the 4.x line.

4.0.0

21 August 2026

17

First stable 4.x: forked, crash-isolated parsing everywhere, JSON configuration, tk: metadata, VLM parsers, in-process Tesseract.

4.1.0

29 September 2026

17

text-recognizers list, less spooling, server presets, Micrometer metrics, ${env:NAME} interpolation.

Is there a 4.2?

Not on the baseline date. The checks made on 2026-10-13:

  • Maven Central tika-core lists 4.1.0 as the latest release (lastUpdated 29 September 2026); the versions after 3.3.2 are 4.0.0-alpha-1, 4.0.0-beta-1, 4.0.0 and 4.1.0.

  • The Apache download area (dlcdn.apache.org/tika/) holds 3.3.2/ and 4.1.0/ only.

  • Docker Hub has 4.2.0-SNAPSHOT and 4.2.0-SNAPSHOT-full tags (nightly builds of the development branch) next to the released 4.1.0, 4.1.0-1, 4.1.0-full, 4.1.0-1-full, latest and latest-full. Never deploy a SNAPSHOT tag.

  • The Git repository has a 3.3.3-rc1 tag, a release candidate, but no 3.3.3 release was announced or published.

  • The 4.1.0 announcement says the project plans to change the default unpack format to Frictionless in 4.2.0 and asks users who would be affected to say so on the dev list. If you extract embedded files with -z, /unpack or ParseMode.UNPACK, set the format explicitly now so that the 4.2 default cannot surprise you (see Embedded Documents & Recursive Parsing).

Support windows

Line Latest Java Namespace Status on 2026-10-13

4.x

4.1.0

17

jakarta

Active development. The line to choose for new work.

3.x

3.3.2

11

jakarta

Maintenance: bug fixes and dependency upgrades. End of support in early 2027 (see below).

2.x

2.9.4

8

javax

End of life since April 2025 (final release May 2025). No fixes.

1.x

1.28.5

8

javax

End of life since 30 September 2022. No fixes.

The 3.x end-of-support date

Two official pages give different dates, and neither has been corrected since 4.0.0 shipped:

  • The roadmap page of the 4.1.x documentation has a version-support table that lists 3.x as supported until March 2027. The same page still says "Only 2.x and 3.x have shipped" and lists 4.x as "not yet released (4.0.0 in progress)", so it was written before 4.0.0 and is stale in at least that respect.

  • The Tika wiki roadmap ("Tika Roadmap — 2.x, 3.x and Beyond", last updated 21 July 2026) gives the planned end of life of 3.x as "January 2027 or 6 months after the release of 4.0.0, whichever comes first". With 4.0.0 announced on 21 August 2026, six months after it is late February 2027, so the rule resolves to January 2027. The same page also keeps an older timeline line that said June 2026.

  • The home page and the download page only say that the 3.x maintenance line "remains supported".

The safe reading is that 3.x support ends some time between January and March 2027, and that every new 3.x release could be the last. Plan the move to 4.x now; use 3.x only while a dependency or a Java 11 runtime forces you to. Re-check both pages before committing to a date, and watch the announce@apache.org list for the end-of-life notice, as the project published one for 1.x and 2.x.

Security fixes that force an upgrade

A version line is only as good as its last security fix. Four CVE records published in the last two years decide the minimum versions to run. The two 2026 records are recent enough that Tika’s own security page did not list them on the baseline date; they were checked in the CVE records themselves.

CVE Published Issue Affected, and the first fixed version

CVE-2025-54988

2025-08-20

Critical XML external entity (XXE) injection through a crafted XFA form inside a PDF.

tika-parser-pdf-module 1.13 to 3.2.1. Fixed in 3.2.2.

CVE-2025-66516

2025-12-04

The same XXE, with a wider scope: the vulnerable code path is also in tika-core, so upgrading only the PDF module was not enough.

tika-core 1.13 to 3.2.1, tika-pdf-module 2.0.0 to 3.2.1, tika-parsers 1.13 to 1.28.5. Fixed in 3.2.2 (all modules at the same version).

CVE-2026-66755

2026-07-30

"Arbitrary Local File Read in ISArchiveParser": relative path traversal in the ISA-Tab parser. A "Study Assay File Name" value in an ISA-Tab investigation file can point outside the dataset directory, and Tika then reads that file into the extracted text. It needs an attacker who can place files in a directory the application later parses. CVSS 4.0 base score 5.9 (medium), local attack vector.

tika-parser-scientific-module 1.8 up to (not including) 3.3.2, and 4.0.0-alpha-1. Fixed in 3.3.2 and 4.0.0-beta-1.

CVE-2026-66756

2026-07-30

"unpack endpoint in tika-server allows configuration with unsecureFeatures=false", classified as improper protection of an alternate path: the /unpack endpoint could be configured past the secure defaults. CVSS 4.0 base score 6.9 (medium), network attack vector.

tika-server 4.0.0-alpha-1 only. Fixed in 4.0.0-beta-1.

What this means for an upgrade plan:

  • Anything at or below 3.2.1 is exposed to a critical XXE through ordinary PDF files. That includes every 1.x and 2.x release, which will never receive a fix: their only remedy is a migration.

  • On 3.x, run 3.3.2. It is the first 3.x release that fixes CVE-2026-66755. The ISA-Tab parser lives in tika-parser-scientific-module, which tika-parsers-standard-package does not include (since 2.0 the scientific parsers are a separate, optional package), so check with mvn dependency:tree whether your build pulls it in.

  • Never run 4.0.0-alpha-1, a preview that is affected by both 2026 records. 4.0.0 and 4.1.0 are not affected.

  • Upgrade every Tika artifact together, from the BOM. CVE-2025-66516 exists because patching one module left the vulnerable code in another.

The CVE pages were checked through the CVE Services API (cveawg.mitre.org/api/cve/<id>), which serves the records that cve.org displays; the titles, affected ranges and CVSS scores above are quoted from those records. The full CVE history, grouped by the lesson each one teaches, is on Security.

Source: the CVE records linked in the table (retrieved 2026-10-13), the Apache announcements they reference (CVE-2026-66755 and CVE-2026-66756) and Apache Tika, Security.

1.x: the Tika of the book

Tika in Action by Chris A. Mattmann and Jukka L. Zitting (Manning, 2011) describes Tika 1.0. Its model is still the model of Tika 4.1, which is why the book remains worth reading:

  • Detection combines magic bytes, file-name globs, XML root elements and container inspection, organised in a media type registry with a type hierarchy (chapters 4 and 11).

  • A Parser turns a stream into XHTML SAX events plus a Metadata object; AutoDetectParser chooses the parser; a ContentHandler decides what to keep (chapter 5).

  • Metadata is a multi-valued map with keys drawn from standards such as Dublin Core (chapter 6).

  • Language identification uses character n-gram profiles (chapter 7).

  • Robustness means running parsers out of process (ForkParser, chapter 9).

What changed is everything around that model. The 1.x line kept going for eleven years after the book, ending with 1.28.5 in September 2022. By then it had added tika-server, RecursiveParserWrapper and the X-TIKA: metadata keys, and later 1.x releases required Java 8. The PMC set 30 September 2022 as the end of life of 1.x and stopped making security fixes after that date.

A 1.x application still in production has two problems at once: it is exposed to CVE-2025-54988 and CVE-2025-66516 (tika-parsers 1.13 to 1.28.5), and it is three API generations behind. Migrate it in one jump to 4.1, using the idiom table further down; there is no benefit in stopping at 2.x or 3.x on the way.

Source: Apache Tika, home page news (the 1.0, 1.28.5 and 1.x end-of-life entries) and Tika in Action, chapters 4 to 7, 9 and 11.

2.0: modules, Pipes and cleaner metadata

Tika 2.0.0 (July 2021) was the first big break. The official "Migrating to Tika 2.0.0" wiki page lists these major changes:

  • The parsers were split into modules. The single 1.x tika-parsers artifact became one module per format family (tika-parser-pdf-module, tika-parser-microsoft-module and so on) and packages that group them: tika-parsers-standard-package, plus optional tika-parser-scientific-package and tika-parser-sqlite3-package (and tika-parser-nlp-package from 2.7.0). Container detection (.doc, .docx, .xlsx) needs the standard package, because it is the parsers that look inside the container.

  • Metadata was cleaned up. Deprecated keys and the duplicate and triplicate keys of early 1.x were removed. For example Author, meta:author and dc:creator collapsed to dc:creator, and title and dc:title to dc:title. Some Java constants moved: Metadata.RESOURCE_NAME_KEY became TikaCoreProperties.RESOURCE_NAME_KEY, and TikaCoreProperties.KEYWORDS was removed in favour of Office.KEYWORDS. X-Parsed-By became X-TIKA:Parsed-By.

  • Language detection became an SPI. The 1.x LanguageIdentifier was removed; detectors implement LanguageDetector and live in tika-langdetect-* modules (Optimaize was the usual choice in 2.x and 3.x).

  • tika-server became tika-server-standard, with the standard parsers only, and ran in --spawnChild mode by default: a watchdog parent restarted a child process that crashed or hung.

  • Configuration moved to tika-config.xml. The .properties files of the PDF, Tesseract and strings parsers were dropped; parsers are configured when they are instantiated.

  • Tika Pipes arrived: PipesClient and PipesServer, fetchers, emitters and pipes iterators.

  • OCR became automatic for PDFs when Tesseract is on the path, which surprised users who then had to switch it off.

  • Logging moved from Log4j 1 to Log4j 2 in tika-app and tika-server (2.0.0-BETA to 2.1.0 were affected by Log4Shell, CVE-2021-44228, through that dependency; upgrade past 2.2.1).

2.x build files

The dependency change is the first thing a 1.x project hits. The 1.x artifact (shown only for recognition):

<!-- Tika 1.x only: one artifact with every parser -->
<dependency>
    <groupId>org.apache.tika</groupId>
    <artifactId>tika-parsers</artifactId>
    <version>1.28.5</version>
</dependency>

The 2.x and 3.x equivalent named the standard package as a jar. In 4.x the same coordinates are a pom (see Build files for 4.x), so a 2.x or 3.x build file needs one more edit when it moves to 4.x.

3.0: Java 11, jakarta and PDFBox 3

Tika 3.0.0 (October 2024) was mostly a platform release. There is no official 2.x to 3.x migration page; the list below comes from the 3.0.0 change log (which lists the BETA, BETA2 and final releases) and the roadmap.

  • Java 11 is required (TIKA-4128), and the 3.x line uses the jakarta namespaces where 2.x used javax (per the version table of the roadmap page).

  • PDFBox 3 replaced PDFBox 2 (TIKA-3347). Applications that also use PDFBox directly must move to 3.x with it; see PDF Parsing.

  • HTML parsing moved from TagSoup to JSoup (TIKA-1599). A custom configuration of HtmlParser has to name org.apache.tika.parser.html.JSoupParser instead.

  • custom-mimetypes.xml is looked up at the classpath root, no longer under /org/apache/tika/mime/ (TIKA-4147).

  • JavaScript is reported as text/javascript instead of application/javascript, following RFC 9239 (TIKA-4119).

  • The boilerpipe handler moved to its own tika-handler-boilerpipe package (TIKA-4138), and Xerces 2 was dropped as a dependency (TIKA-4135).

  • AbstractParser was deprecated for removal in 4.x (TIKA-4132): implement Parser directly.

  • tika-grpc was added in 3.0.0-BETA2 (TIKA-4181); see Tika gRPC.

  • Convenience methods for XML readers moved from ParseContext to XMLReaderUtils (TIKA-4259).

Later 3.x minor releases changed behaviour too, which matters when you compare a 3.x baseline with 4.x output: 3.2.2 fixed the XFA XXE, 3.3.0 moved to poi-ooxml-full, and 3.3.2 made the SAX-based OOXML parsers the default and put /pipes, /async and /status behind enableUnsecureFeatures=true. Compare 4.x output with 3.3.2 output, not with an older 3.x.

4.0 and 4.1: the biggest break since 2.0

Tika 4.0.0 (21 August 2026) changed the platform, the packaging, the configuration format, the core SPI, the default output, the metadata keys and the server, all at once. The official "notable changes" page groups the breaking changes; this section follows the same grouping and links each item to the page of this section that covers it.

flowchart LR subgraph P["Platform and packaging"] P1["Java 17"] P2["tika-app, tika-server: zip with lib/ and plugins/"] P3["tika-parsers-standard-package is a pom"] end subgraph C["Configuration"] C1["tika-config.json and TikaLoader"] C2["TikaConfig removed"] end subgraph A["Java API"] A1["parse and detect take TikaInputStream"] A2["no implicit spooling"] A3["unified timeout model"] A4["ForkParser removed, PipesForkParser"] end subgraph O["Output"] O1["Markdown by default"] O2["tk: metadata keys"] end subgraph S["Services"] S1["server parses in forked JVMs"] S2["allowPipes, allowPerRequestConfig"] S3["429, 503, 400, 413"] end P --> C --> A --> O --> S

Platform and packaging

  • Java 17 or later (3.x ran on Java 11).

  • Zip distributions. tika-app and tika-server-standard ship as zips whose jar is a thin launcher next to lib/ and plugins/. The bare jar fails with NoClassDefFoundError. This catches tika-server-standard hardest, because its jar is still on Maven Central. Container images and scripts that copied only the jar must unzip the distribution instead; see Setup & Installation and Docker & Kubernetes Deployment.

  • tika-parsers-standard-package is a pom: Maven needs <type>pom</type>, Gradle @pom.

  • Release channels split. Maven Central has slim per-module jars, the Apache download area the runnable zips and the pf4j plugin zips, Docker Hub the images.

  • Modules removed with no direct replacement: tika-batch (and the -bc batch mode of tika-app), tika-dl, the advanced media module, tika-fuzzing, tika-age-recogniser, tika-server-eval, the .NET bindings and the snap packages. The old tika-langdetect-tika module was removed too, and CharSoup became the bundled language detector.

Configuration

  • JSON replaces XML. TikaConfig and the XML configuration API are removed; TikaLoader from tika-serialization loads tika-config.json. Components are referenced by registered names (pdf-parser), not class names.

  • tika-app --convert-config-xml-to-json converts the parsers section of a 3.x file; every other section is a manual rewrite. See Configuration.

  • Parsers and detectors no longer expose bean setters for their settings; options live in per-component config objects passed through the ParseContext (TIKA-4758).

Java API

  • TikaInputStream in the SPI. Parser.parse takes a TikaInputStream; Detector.detect takes (TikaInputStream, Metadata, ParseContext); EmbeddedDocumentExtractor methods gain a ParseContext. Every third-party parser, detector and extractor must be updated, and the break is a compile error, not a silent change. The Tika facade still accepts an InputStream, but Tika.detect(InputStream) no longer returns the caller’s stream at its original position. See Parser API & AutoDetectParser.

  • No implicit spooling. A TikaInputStream is consumed in passthrough mode unless enableRewind() is called at position 0; rewind(), getFile() or getPath() after reading without it throw instead of spooling to disk.

  • Unified timeout model. A total-task budget plus a progress (stall) timeout, shared by the library, tika-app, Pipes and the server. TikaTimeoutException is now a checked exception, and *TimeoutSeconds / *TimeoutMs settings became *TimeoutMillis. See Robustness, Limits & Timeouts.

  • ForkParser and the whole org.apache.tika.fork package are removed; PipesForkParser (module tika-pipes-fork-parser) replaces them.

  • Encoding detectors moved to org.apache.tika.detect.* and new tika-encoding-detector-* modules, with a new default pipeline; see Character Encoding Detection.

  • EmbeddedDocumentExtractor is stateless, and the extractor factories (EmbeddedDocumentExtractorFactory and friends) were deleted; bind an extractor instance in the ParseContext. See Embedded Documents & Recursive Parsing.

  • ExternalParser is configuration-only: CompositeExternalParser, ExternalParsersFactory and the XML auto-discovery are gone; each external tool is an explicit JSON entry. See Extending Tika.

  • Other removals: Metadata#setAll(Properties) (use putAll(Metadata)), TikaMimeKeys, CreativeCommons, the DOM-based OOXML extractors, ParserUtils.EMBEDDED_PARSER, TikaCoreProperties.TIKA_CONTENT_HANDLER (use TIKA_CONTENT_HANDLER_TYPE), the ...Choise misspelled Property factories (renamed to ...Choice), and the writefilter package (renamed writelimiter).

Output

  • Markdown is the default content format for tika-app, tika-server (/tika, /rmeta) and the pipes CLI. Ask for XHTML with -x, /tika/xml or /rmeta/xml. See Content Handlers & Output Formats.

  • Metadata keys moved to the tk: namespace in kebab-case (tk:resource-name, tk:parsed-by, tk:content), replacing X-TIKA:, tika:, tika_pg:, rendering:, signature:, imagereader: and bare names such as resourceName. TikaCoreProperties constants keep compiling. The opt-in legacy-key-migration-filter restores 3.x spellings at the emit edge. String-keyed writes to reserved keys now throw. Metadata no longer implements HttpHeaders, Message, TIFF and the other constant interfaces, so Metadata.CONTENT_TYPE becomes HttpHeaders.CONTENT_TYPE. See Metadata.

Services

  • tika-server parses in forked JVMs through Tika Pipes, always. numClients is both the concurrency ceiling and the memory footprint; each fork’s heap is set with pipes.forkedJvmArgs.

  • enableUnsecureFeatures is gone (a config that still has it fails to start) and is split into allowPipes and allowPerRequestConfig, both default-deny.

  • Accurate status codes: 429 when every fork is busy, 503 when a fork crashed, timed out or ran out of memory, 400 for unknown fetchers and handlers, 413 for oversized bodies. Error bodies are JSON.

  • Endpoints removed or merged: /translate/*, /tika/main, the /tika/form family; /detect/stream is /detect; /language/stream and /language/string are /language. The Accept header no longer selects the output format.

  • Per-request configuration headers removed (writeLimit, X-Tika-Handler, meta_*, X-Tika-OCR*, X-Tika-PDF*) and silently ignored if sent.

  • taskTimeoutMillis became parse-context.timeout-limits.totalTaskTimeoutMillis, and its default grew from 5 minutes to 1 hour. No startup error flags this one.

  • tika-grpc’s generated Java classes moved to org.apache.tika.pipes.grpc.proto; the wire protocol did not change.

The full server delta, with the 3.x XML and the 4.x JSON side by side, is on Tika Server (REST API); the command line delta is on Tika App (Command Line & GUI); gRPC is on Tika gRPC.

New in 4.0

  • Vision-language-model parsers (Claude, Gemini, OpenAI-compatible) in tika-vlm and the tika-inference module; see VLM Parsers & Inference.

  • In-process Tesseract through tika-parser-tess4j-module; see OCR with Tesseract.

  • Pipes parse modes NO_PARSE, CONTENT_ONLY and UNPACK, and new pipes plugins (Elasticsearch emitter, Google Drive, Microsoft Graph, Azure Blob, JSON, HTTP, Atlassian JWT, an Ignite config store); see Tika Pipes.

  • Small documents travel to the fork inside the request (pipes.maxInlineBytes, 10 MB by default) instead of through a temporary file.

  • A Markdown parser with structured XHTML output, unified XMP extraction (HEIF, WebP, motion photos), content-based detection of PKCS#7, PKCS#12 and timestamped-data containers (files that were application/octet-stream in 3.x may now get a crypto type), and audio cover art as embedded documents (embedded counts for audio files change).

  • tika.extras.dir to load extra jars into tika-app and tika-server without repackaging (a trusted code location).

  • Rewritten charset, junk-text and language detection, with CharSoup as the bundled language detector; see Language Detection & Translation.

  • Dependency jumps such as Jetty 11 to 12.1, CXF 4.0 to 4.2 and SolrJ 8.11 to 10.0.

What 4.1 changed over 4.0

4.1.0 is a minor release, but it has its own "Breaking Changes and Deprecations" list. Read it before moving from 4.0:

Change Effect on an upgrade from 4.0

OCR and enrichment engines are selected by name in a text-recognizers list (TIKA-4872, TIKA-4884)

The image/ocr-* pseudo-types are retired; engines under parsers are deprecated. Move OCR configuration to engines plus text-recognizers; an empty list ([]) turns enrichment off.

PDF page settings become the pages block

Move per-page rendering and OCR options of the PDF parser there; see PDF Parsing.

exception-reporting in parse-context (TIKA-4848)

tk:exception:* values may now start with the TikaException wrapper line; code that matched on the first line must be updated.

Pipes IPC carries inline bytes as a raw binary field (TIKA-4829)

4.0.0 tuples with an inline-bytes parse-context entry are rejected.

Pipes plugins no longer bundle Jackson (TIKA-4840)

Plugin configuration is parsed by a strict shared mapper that rejects unknown and duplicate keys.

OpenAIVLMParser no longer auto-registers through SPI (TIKA-4871)

Configure it explicitly.

Temp files follow java.io.tmpdir (TIKA-4877)

pipes.tempDirectory is deprecated.

Kafka pipes iterator (TIKA-4833)

No longer stops on the first empty poll; groupInitialRebalanceDelayMs is deprecated.

${env:NAME} interpolation in tika-config.json (TIKA-4909)

Secrets no longer need to be written into the file.

Server presets (TIKA-4856) and Micrometer metrics (TIKA-4839)

New, opt-in.

With a Content-Type override set, DefaultDetector no longer lets a more specific magic result overrule it (TIKA-4868, the same combined PERFORMANCE entry that covers the faster magic detection and Markdown output)

Detection results can change for callers that pass an override.

PipesForkParser honours javaPath and socketTimeoutMillis set in code (TIKA-4931)

In 4.0.0 they were silently dropped.

embedded-limits.maxDepth counts embedding levels again (TIKA-4857)

In 4.0.0 values above 1 stopped one level early.

tika-inference and tika-vlm

Still experimental: classes, keys and output may change in minor releases.

Outdated idiom to current code

The table maps the idioms of Tika in Action to their Tika 4.1 form. The book was written for Tika 1.0; its listings are grouped here by the idiom they use rather than reproduced, and the chapter is the one that introduces the idiom. Rows marked "verified" were compiled (and, where they produce output, run) against Tika 4.1.0 for this page; the others cite the page that verified them.

Ch. Book-era idiom (1.x) Current code (4.1) Notes

Facade and command line

1, 3

new Tika().parseToString(file) and tika.detect(file)

Unchanged: tika.parseToString(path), tika.detect(path). Prefer Path overloads.

Verified. Still runs in your JVM: fine for trusted files, not for hostile input. Default limit 100,000 characters.

3

new Tika(TikaConfig.getDefaultConfig())

new Tika(), or new Tika(detector, parser) with both loaded from a TikaLoader

TikaConfig removed in 4.0. See the facade page.

3

tika.detect(stream) then reading the same stream

try (TikaInputStream tis = TikaInputStream.get(path)), detect, then tis.rewind() after enableRewind(), or re-open the source

4.x returns the caller’s stream advanced.

2

java -jar tika-app-1.0.jar --server --port 1234

Run tika-server-standard from its zip, or the apache/tika:4.1.0-1 image, and call it over HTTP

--server removed in 2.0; 4.1.0 answers "Unknown option".

2

java -jar tika-app.jar copied alone into a directory

Unzip tika-app-4.1.0.zip and run the jar next to lib/ and plugins/

The bare 4.x jar fails with NoClassDefFoundError.

2

java -jar tika-app.jar -x file as the default output

tika -x file for XHTML; the default is --md

Markdown default since 4.0.

Media types and detection

4

TikaConfig.getDefaultConfig().getMimeRepository()

TikaLoader.getMimeTypes() or MimeTypes.getDefaultMimeTypes()

Verified (both exist in 4.1.0).

4

TikaConfig.getDefaultConfig().getMediaTypeRegistry()

TikaLoader.getMediaTypeRegistry()

Verified.

4

detector.detect(stream, metadata) with metadata.set(Metadata.RESOURCE_NAME_KEY, name)

detector.detect(tis, metadata, new ParseContext()) with metadata.set(TikaCoreProperties.RESOURCE_NAME_KEY, name)

Verified. The key is written as tk:resource-name.

11

custom-mimetypes.xml under org/apache/tika/mime/

custom-mimetypes.xml at the classpath root

Changed in 3.0 (TIKA-4147). See Media Types & Detection.

4

ICU4J CharsetDetector as the charset answer

AutoDetectReader, or the 4.x encoding-detector chain (BOM, metadata, HTML, Mojibuster, junk filter)

ICU4J and juniversalchardet are optional modules in 4.x. See Character Encoding Detection.

Parsing and content handlers

5

parser.parse(inputStream, handler, metadata, new ParseContext())

Wrap first: try (TikaInputStream tis = TikaInputStream.get(path)) { parser.parse(tis, handler, metadata, context); }

Verified. There is no InputStream overload in 4.x.

5

new AutoDetectParser() with a TikaConfig

TikaLoader.load(path).loadAutoDetectParser(), one shared instance

Verified. new AutoDetectParser() still works for defaults.

5

new BodyContentHandler() and relying on its default

new BodyContentHandler(limit) with an explicit number, or output-limits.writeLimit with a context-built handler

The 100,000-character default is silent truncation.

5

new ToXMLContentHandler() because XHTML was the only rich format

new ToMarkdownContentHandler() or BasicContentHandlerFactory.HANDLER_TYPE.MARKDOWN for models and chunkers; XHTML still available

See Content Handlers & Output Formats.

5

LinkContentHandler, TeeContentHandler, ParsingReader

Unchanged classes

Verified (present in 4.1.0).

5

TikaInputStream.get(file) that silently spools to a temporary file

TikaInputStream.get(path); call enableRewind() at position 0 if you need to read twice

No implicit spooling since 4.0.

Metadata

6

metadata.get(Metadata.TITLE), metadata.get(Metadata.AUTHOR)

metadata.get(TikaCoreProperties.TITLE) (dc:title), metadata.get(TikaCoreProperties.CREATOR) (dc:creator)

Verified. The 1.x string constants were deprecated in 1.x and removed by 2.0.

6

MSOffice.AUTHOR, MSOffice.PAGE_COUNT

TikaCoreProperties.CREATOR, Office.PAGE_COUNT (meta:page-count)

MSOffice no longer exists in 4.1.0 (verified).

6

metadata.get(Metadata.CONTENT_TYPE)

metadata.get(HttpHeaders.CONTENT_TYPE)

Verified: the old form fails with "cannot find symbol: variable CONTENT_TYPE".

6

String keys such as X-Parsed-By, resourceName, X-TIKA:content

TikaCoreProperties.TIKA_PARSED_BY (tk:parsed-by), RESOURCE_NAME_KEY (tk:resource-name), TIKA_CONTENT (tk:content)

Use constants, not strings. Old consumers: legacy-key-migration-filter. See Metadata.

6

metadata.set("X-Shelf", value) with any string, including Tika’s own keys

metadata.set(myProperty, value) with a key in your own prefix

Writing a reserved tk: key by string throws since 4.0.

Embedded documents

5, 8

context.set(Parser.class, parser) to parse attachments, text merged into one stream

RecursiveParserWrapper with a RecursiveParserWrapperHandler: one Metadata per document, tk:embedded-resource-path on each

Verified. AutoDetectParser sets Parser itself. See Embedded Documents & Recursive Parsing.

8

A custom EmbeddedDocumentExtractor with per-parse state

A stateless extractor bound in the ParseContext; embedded-limits for depth and count

The extractor factories were deleted in 4.0.

Language

7

new LanguageIdentifier(text).getLanguage()

LanguageDetector.getDefaultLanguageDetector().loadModels().detect(text).getLanguage()

Verified. Codes are ISO 639-3 (spa), not two letters. See Language Detection & Translation.

7

ProfilingHandler, LanguageProfile

LanguageHandler; models are internal to each detector

Removed in 2.0.

Robustness and scale

9

new ForkParser(classLoader, parser)

new PipesForkParser(new PipesForkParserConfig()), then parse(tikaInputStream) and check getStatus()

Verified. ForkParser removed in 4.0. See Robustness, Limits & Timeouts.

9

One AutoDetectParser shared between threads, in-process, for any file

Same sharing rule, but untrusted files go to PipesForkParser, tika-server, tika-grpc or Tika Pipes

In-process denial of service is not a vulnerability under the security model.

9

Your own thread pool and directory loop (later tika-batch, -bc)

Tika Pipes: a file-system iterator, fetcher and emitter, or tika -a

tika-batch removed in 4.0. See Tika Pipes.

Configuration and extension

11

new TikaConfig(new File("tika-config.xml"))

TikaLoader.load(Path.of("tika-config.json"))

Verified. XML is not read at all.

11

<parser class="org.apache.tika.parser.pdf.PDFParser">

{ "pdf-parser": { ... } } inside the parsers array

Component names, not class names.

11

extends AbstractParser plus a hand-written META-INF/services file

implements Parser with parse(TikaInputStream, ...), annotated @TikaComponent

AbstractParser deprecated for removal since 3.0. See Extending Tika.

11

tika-external-parsers.xml auto-discovery

An explicit external-parser entry in tika-config.json per tool

Auto-discovery removed in 4.0.

Integration

10

Lucene 3 new Field("content", text, Field.Store.NO, Field.Index.ANALYZED)

new TextField("content", text, Field.Store.NO), or a Tika Pipes emitter for Solr, Elasticsearch or OpenSearch

The Lucene 3 field API is gone. See Search Indexing with Lucene, Solr & Elasticsearch.

12 to 15

Case studies (Nutch, Jackrabbit, OODT and others) embedding Tika in-process

Read them as history; current integrations are Solr Cell, the Elasticsearch attachment processor, Pipes emitters and RAG loaders

See RAG & LLM Ingestion.

The 1.x column describes the idiom; the book’s own listings are not reproduced. The chapter numbers and the individual listing numbers were not re-checked against a copy of the book for this page.

A complete migration: the Bookshelf intake

The Bookshelf intake service detects a renamed file, extracts text and metadata from a manuscript, identifies the language of a review, walks the attachments of a publisher e-mail and isolates PDF parsing in a separate process. The three listings below do the same job in the style of the book, of 3.x and of 4.1.

The book-era version (1.x, do not use)

Written for this page in the style of Tika in Action; it does not compile against any current Tika and was not run.

// Tika 1.x style. Every marked line is removed or changed in later versions.
TikaConfig config = new TikaConfig(new File("tika-config.xml"));              // 4.0: TikaLoader
Parser parser = new AutoDetectParser(config);

Metadata metadata = new Metadata();
metadata.set(Metadata.RESOURCE_NAME_KEY, "manuscript.docx");                 // 2.0: TikaCoreProperties
InputStream stream = new FileInputStream("manuscript.docx");                 // no try-with-resources
ContentHandler handler = new BodyContentHandler();                           // silent 100,000 limit
ParseContext context = new ParseContext();
context.set(Parser.class, parser);                                           // attachments merged into one text
parser.parse(stream, handler, metadata, context);                            // 4.0: TikaInputStream
System.out.println(metadata.get(Metadata.TITLE));                            // 2.0: removed
System.out.println(metadata.get(Metadata.CONTENT_TYPE));                     // 4.0: HttpHeaders

LanguageIdentifier identifier = new LanguageIdentifier(handler.toString());  // 2.0: removed
System.out.println(identifier.getLanguage());                               // "es", not "spa"

ForkParser fork = new ForkParser(getClass().getClassLoader(), parser);       // 4.0: removed

The 3.x version

Shown for readers migrating a 3.x code base. It compiles against 3.3.2 in shape but was not built for this page.

// Tika 3.x only.
TikaConfig config = new TikaConfig(Path.of("tika-config.xml"));
Parser parser = new AutoDetectParser(config);

Metadata metadata = new Metadata();
try (InputStream stream = Files.newInputStream(Path.of("manuscript.docx"))) {
    parser.parse(stream, new BodyContentHandler(1_000_000), metadata, new ParseContext());
}
System.out.println(metadata.get(TikaCoreProperties.TITLE));
System.out.println(metadata.get(Metadata.CONTENT_TYPE));       // still inherited in 3.x
System.out.println(metadata.get("X-TIKA:Parsed-By"));          // string key, renamed in 4.0

LanguageDetector detector = new OptimaizeLangDetector().loadModels();   // tika-langdetect-optimaize
System.out.println(detector.detect(text).getLanguage());

ForkParser fork = new ForkParser(MyApp.class.getClassLoader(), parser);  // removed in 4.0

The 4.1 version

The same work with current APIs. Dependencies: tika-core, tika-parsers-standard-package (as a pom), tika-serialization and tika-pipes-fork-parser, all 4.1.0 from tika-bom.

tika-config.json, next to the sample files:

{
  "parsers": [
    { "default-parser": {} }
  ],
  "parse-context": {
    "output-limits": {
      "writeLimit": 1000000,
      "throwOnWriteLimit": false
    },
    "embedded-limits": {
      "maxDepth": 5,
      "maxCount": 100
    }
  }
}
import java.nio.file.Path;
import java.util.List;

import org.apache.tika.Tika;
import org.apache.tika.config.loader.TikaLoader;
import org.apache.tika.io.TikaInputStream;
import org.apache.tika.language.detect.LanguageDetector;
import org.apache.tika.language.detect.LanguageResult;
import org.apache.tika.metadata.HttpHeaders;
import org.apache.tika.metadata.Metadata;
import org.apache.tika.metadata.TikaCoreProperties;
import org.apache.tika.parser.ParseContext;
import org.apache.tika.parser.Parser;
import org.apache.tika.parser.RecursiveParserWrapper;
import org.apache.tika.pipes.fork.PipesForkParser;
import org.apache.tika.pipes.fork.PipesForkParserConfig;
import org.apache.tika.pipes.fork.PipesForkResult;
import org.apache.tika.sax.BasicContentHandlerFactory;
import org.apache.tika.sax.BodyContentHandler;
import org.apache.tika.sax.RecursiveParserWrapperHandler;

/** The Bookshelf intake idioms of the book, rewritten for Tika 4.1. */
public class MigratedIntake {

    public static void main(String[] args) throws Exception {
        Path inbox = Path.of(args.length > 0 ? args[0] : ".");

        // 1. Configuration: TikaLoader and tika-config.json replace TikaConfig and tika-config.xml.
        TikaLoader loader = TikaLoader.load(inbox.resolve("tika-config.json"));
        Parser parser = loader.loadAutoDetectParser();

        // 2. Detection by content: the facade still accepts a Path.
        Tika tika = new Tika();
        System.out.println("detect   renamed-invoice.png -> " + tika.detect(inbox.resolve("renamed-invoice.png")));

        // 3. Parsing: a TikaInputStream, an explicit write limit and typed metadata keys.
        Metadata metadata = new Metadata();
        BodyContentHandler handler = new BodyContentHandler(1_000_000);
        try (TikaInputStream tis = TikaInputStream.get(inbox.resolve("manuscript.docx"))) {
            parser.parse(tis, handler, metadata, loader.loadParseContext());
        }
        System.out.println("parse    " + HttpHeaders.CONTENT_TYPE + " = " + metadata.get(HttpHeaders.CONTENT_TYPE));
        System.out.println("parse    " + TikaCoreProperties.TITLE.getName() + " = " + metadata.get(TikaCoreProperties.TITLE));
        System.out.println("parse    " + TikaCoreProperties.CREATOR.getName() + " = " + metadata.get(TikaCoreProperties.CREATOR));
        System.out.println("parse    " + TikaCoreProperties.TIKA_PARSED_BY.getName() + " = "
                + String.join(", ", metadata.getValues(TikaCoreProperties.TIKA_PARSED_BY)));

        // 4. Language: the LanguageDetector SPI replaces LanguageIdentifier.
        String review = tika.parseToString(inbox.resolve("reviews-es.html"));
        LanguageDetector detector = LanguageDetector.getDefaultLanguageDetector().loadModels();
        LanguageResult language = detector.detect(review);
        System.out.println("language reviews-es.html -> " + language.getLanguage() + " " + language.getConfidence());

        // 5. Embedded documents: RecursiveParserWrapper replaces context.set(Parser.class, parser).
        RecursiveParserWrapper wrapper = new RecursiveParserWrapper(parser);
        RecursiveParserWrapperHandler rpw = new RecursiveParserWrapperHandler(
                new BasicContentHandlerFactory(BasicContentHandlerFactory.HANDLER_TYPE.TEXT, 100_000));
        try (TikaInputStream tis = TikaInputStream.get(inbox.resolve("publisher-email.eml"))) {
            wrapper.parse(tis, rpw, new Metadata(), new ParseContext());
        }
        List<Metadata> list = rpw.getMetadataList();
        for (Metadata m : list) {
            String path = m.get(TikaCoreProperties.EMBEDDED_RESOURCE_PATH);
            System.out.println("embedded " + (path == null ? "(container)" : path) + "  " + m.get(HttpHeaders.CONTENT_TYPE));
        }

        // 6. Crash isolation: PipesForkParser replaces ForkParser.
        try (PipesForkParser forked = new PipesForkParser(new PipesForkParserConfig().addJvmArg("-Xmx512m"));
             TikaInputStream tis = TikaInputStream.get(inbox.resolve("catalogue.pdf"))) {
            PipesForkResult result = forked.parse(tis);
            System.out.println("forked   catalogue.pdf -> " + result.getStatus() + ", "
                    + result.getContent().strip().lines().findFirst().orElse(""));
        }
    }
}

Output (Tika 4.1.0, Java 21; log lines on standard error omitted):

detect   renamed-invoice.png -> application/pdf
parse    Content-Type = application/vnd.openxmlformats-officedocument.wordprocessingml.document
parse    dc:title = The Lighthouse Keeper
parse    dc:creator = Ana Torres
parse    tk:parsed-by = org.apache.tika.parser.DefaultParser, org.apache.tika.parser.microsoft.ooxml.OOXMLParser, org.apache.tika.parser.microsoft.ooxml.xwpf.XWPFEventBasedWordExtractor
language reviews-es.html -> spa MEDIUM
embedded (container)  message/rfc822
embedded /samples.zip/chapter-one.docx/figure1.png  image/png
embedded /samples.zip/chapter-one.docx  application/vnd.openxmlformats-officedocument.wordprocessingml.document
embedded /samples.zip/catalogue.pdf  application/pdf
embedded /samples.zip/cover-note.txt  text/plain; charset=windows-1252
embedded /samples.zip  application/zip
forked   catalogue.pdf -> PARSE_SUCCESS, Spring catalogue

What the output shows about the migration:

  • The keys printed by name are the 4.x names: dc:title and dc:creator are unchanged since 2.0, tk:parsed-by is X-TIKA:Parsed-By in 3.x and X-Parsed-By in 1.x. Code that used the constants needed no change for them.

  • The language code is spa (ISO 639-3), where the 1.x LanguageIdentifier returned es. Every stored language value and every filter on it changes with the migration.

  • RecursiveParserWrapper lists the attachments depth-first, children before their container, and returns the container first. The 1.x recipe would have produced one merged text with no paths.

  • The fork takes a few seconds to start the first time (the run took about 9 seconds in total); share one PipesForkParser for the life of the application rather than one per file.

  • The fork logs "No plugins" and a missing plugins root at INFO and WARN. That is expected when no pipes plugins are installed next to the program and does not affect a PipesForkParser that reads local files.

Detection with an explicit detector

The book’s detection listings call a Detector directly with the file name as a hint. In 4.1 the detector takes a TikaInputStream and a ParseContext, and the name goes under TikaCoreProperties.RESOURCE_NAME_KEY:

import java.nio.file.Path;
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.TikaCoreProperties;
import org.apache.tika.mime.MediaType;
import org.apache.tika.mime.MediaTypeRegistry;
import org.apache.tika.parser.ParseContext;

public class DetectByName {
    public static void main(String[] args) throws Exception {
        Detector detector = TikaLoader.loadDefault().loadDetectors();
        MediaTypeRegistry registry = TikaLoader.getMediaTypeRegistry();
        Path file = Path.of("renamed-invoice.png");
        Metadata metadata = new Metadata();
        metadata.set(TikaCoreProperties.RESOURCE_NAME_KEY, file.getFileName().toString());
        try (TikaInputStream tis = TikaInputStream.get(file)) {
            MediaType type = detector.detect(tis, metadata, new ParseContext());
            System.out.println(type + " (supertype " + registry.getSupertype(type) + ")");
        }
    }
}

Output (Tika 4.1.0):

application/pdf (supertype application/octet-stream)

The .png name is only a hint; the bytes win. The same program written against 1.x would use new DefaultDetector(), Metadata.RESOURCE_NAME_KEY and a plain InputStream, none of which compiles unchanged today.

Build files for 4.x

Import the BOM so that every Tika artifact has the same version; never mix tika-core and parser modules of different versions (CVE-2025-66516 is the reason). The coordinates were resolved for Setup & Installation.

<dependencyManagement>
    <dependencies>
        <dependency>
            <groupId>org.apache.tika</groupId>
            <artifactId>tika-bom</artifactId>
            <version>4.1.0</version>
            <type>pom</type>
            <scope>import</scope>
        </dependency>
    </dependencies>
</dependencyManagement>

<dependencies>
    <dependency>
        <groupId>org.apache.tika</groupId>
        <artifactId>tika-core</artifactId>
    </dependency>
    <dependency>
        <groupId>org.apache.tika</groupId>
        <artifactId>tika-parsers-standard-package</artifactId>
        <type>pom</type> <!-- new in 4.0: a pom, not a jar -->
    </dependency>
    <dependency>
        <groupId>org.apache.tika</groupId>
        <artifactId>tika-serialization</artifactId> <!-- TikaLoader -->
    </dependency>
    <dependency>
        <groupId>org.apache.tika</groupId>
        <artifactId>tika-pipes-fork-parser</artifactId> <!-- PipesForkParser -->
    </dependency>
</dependencies>

The same in Gradle Kotlin DSL:

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@pom")
    implementation("org.apache.tika:tika-serialization")
    implementation("org.apache.tika:tika-pipes-fork-parser")
}

Dependencies that need attention when you migrate:

From To

tika-parsers (1.x)

tika-parsers-standard-package (pom), plus tika-parser-scientific-package and tika-parser-sqlite3-package only if you used those formats

tika-langdetect-optimaize (2.x and 3.x)

tika-langdetect-charsoup (recommended in 4.x) or tika-langdetect-opennlp; re-validate thresholds

tika-langdetect-tika (2.x and 3.x)

Removed in 4.0; CharSoup replaces it

tika-batch

Removed; Tika Pipes

tika-server-standard jar from Maven Central as a runnable

The zip distribution or the apache/tika image

Your own PDFBox 2.x or POI version

PDFBox 3 and the POI version of the Tika BOM; a different major does not work

The Gradle snippet uses the @pom classifier that the 4.0.0 notable-changes page names for the standard package; it was not built with Gradle for this page.

Configuration, metadata and services in one view

The three changes that break most 3.x deployments, each with its migration tool and the page that has the detail.

Change 3.x 4.x Migration aid

Configuration

tika-config.xml, TikaConfig, class names

tika-config.json, TikaLoader, component names

tika-app --convert-config-xml-to-json (parsers only). Configuration

Metadata keys

X-TIKA:Parsed-By, resourceName, X-TIKA:content

tk:parsed-by, tk:resource-name, tk:content

legacy-key-migration-filter and metadata-migration-3x-4x.json. Metadata

Server

enableUnsecureFeatures, headers, Accept routing

allowPipes, allowPerRequestConfig, parse-context, explicit paths

Startup fails on unknown keys, which lists them. Tika Server (REST API)

A consumer that cannot move to the new key names at once can keep reading 3.x names while the producer runs 4.x:

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

The filter rewrites keys at the emit edge (V4_TO_V3 by default). Treat it as a bridge for a migration window, not as a permanent setting: new 4.x keys have no 3.x name, and some 3.x keys were dropped rather than renamed.

Migration checklist: 3.x to 4.1

The order matters: each step makes the next one easier to test.

flowchart TD A["Upgrade to 3.3.2 and record a baseline: text, metadata, status per sample file"] --> B["Java 17 or later on every host"] B --> C["Build files: tika-bom 4.1.0, standard package as pom, tika-serialization"] C --> D["Code: TikaInputStream in parse and detect, TikaLoader, HttpHeaders.CONTENT_TYPE"] D --> E["Config: convert parsers, rewrite the rest as JSON, limits in parse-context"] E --> F["Metadata: tk: keys in code and index, or legacy-key-migration-filter for a window"] F --> G["Output: request XHTML explicitly or adopt Markdown"] G --> H["Isolation: ForkParser to PipesForkParser, server forks sized"] H --> I["Deploy: zip distributions or pinned apache/tika:4.1.0-1 images"] I --> J["Compare with the 3.3.2 baseline, then switch traffic"]
  1. Upgrade to 3.3.2 first and record a baseline for a corpus of your own files: detected type, text length, the metadata keys you use, the parse status. 3.3.2 already has the SAX OOXML parsers and the CVE fixes, so a later difference is a 4.x change, not a 3.x bug fix. tika-eval & Extraction Quality compares two extraction runs systematically.

  2. Move every host and container to Java 17 or later.

  3. Change the build: BOM 4.1.0, tika-parsers-standard-package as pom, add tika-serialization and, for isolation, tika-pipes-fork-parser. Remove tika-batch, tika-langdetect-tika and any separately pinned PDFBox 2.

  4. Fix the compile errors: wrap streams in TikaInputStream, add ParseContext to detectors and extractors, replace TikaConfig with TikaLoader, replace inherited Metadata constants (HttpHeaders.CONTENT_TYPE), replace ForkParser. Custom parsers implement Parser, not AbstractParser.

  5. Check the code that compiles but behaves differently: streams are no longer rewound by Tika.detect, nothing spools unless asked, TikaTimeoutException is checked, and string writes to tk: keys throw.

  6. Migrate the configuration to tika-config.json: convert parsers, rewrite the rest, put limits in parse-context, move OCR to engines and text-recognizers, reference secrets as ${env:NAME}. Load it once at startup so that a bad key fails the deployment, not the first request.

  7. Migrate metadata consumers: search code, index schemas and dashboards for X-TIKA: and the other old prefixes; rename, or enable legacy-key-migration-filter while they catch up. Re-index if the index stores the key names.

  8. Decide the output format: adopt Markdown (good for chunkers and models, see RAG & LLM Ingestion) or request XHTML explicitly everywhere.

  9. Migrate the services: server config to JSON with allowPipes/allowPerRequestConfig, clients to explicit handler paths and to handling 429 and 503, numClients and fork heap sized deliberately (see Performance & Scaling), and the timeout set on purpose (the default grew from 5 minutes to 1 hour).

  10. Deploy from the zip distributions or pinned images, never a bare jar or a SNAPSHOT tag.

  11. Compare with the 3.3.2 baseline and explain every difference before switching traffic; the integration tests of Testing Tika Integrations are the place to keep that corpus.

For a Spring Boot application the code changes are the same; the wiring is shown on Spring Boot Integration.

Source: Apache Tika 4.1.x, Migration to 4.x and the guides it links (migrating to 4.x, migrating tika-server, metadata changes).

Upgrading from 1.x or 2.x

  • Go straight to 4.1. Both lines are end of life and affected by the 2025 XXE CVEs; an intermediate stop on 3.x only adds a second migration.

  • From 1.x, apply the 2.0 changes first in your head, then the 4.x ones: the artifact split, the metadata key clean-up (Author to dc:creator), the removal of LanguageIdentifier, and then everything in the 3.x checklist above. The idiom table covers the book-era code.

  • From 2.x, the Java jump is from 8 to 17, the javax to jakarta change affects anything that shares a servlet or JAX-RS container with Tika, PDFBox goes from 2 to 3 and HTML parsing from TagSoup to JSoup, so the HTML text and structure you indexed can change. Then apply the 3.x checklist.

  • Expect output differences on every format: three major versions of parser fixes, a new HTML parser, SAX OOXML parsing, a new charset pipeline and a new language detector. Measure them with a corpus; do not assume equality.

Pitfalls

  • Mixing generations of examples. A 3.x answer with TikaConfig or X-TIKA: keys does not apply to 4.x; check the version of every snippet you copy, including the official 3.3.2 examples.html, which still shows the 1.x LanguageIdentifier.

  • Trusting a green build. The worst 4.x changes compile: silently ignored server headers, the 1-hour default timeout, Markdown instead of XHTML, tk: keys that an index schema does not know.

  • Upgrading one module. Different versions of tika-core and the parser modules are unsupported and were the root of CVE-2025-66516.

  • Running a preview in production. 4.0.0-alpha-1 has two CVEs; SNAPSHOT images change every night.

  • Reading the stale roadmap table as current. It predates 4.0.0; check the wiki and the news page too.

  • Forgetting the 4.2 default change. Pin the unpack format if you rely on the current one.

What was verified and what was not

Verified on 2026-10-13:

  • Release versions and dates on the Apache Tika news page, Maven Central tika-core metadata, the Apache download area, Docker Hub tags and the GitHub tag list; the absence of a 4.2 release.

  • Both roadmap pages, quoted above, and the 1.x and 2.x end-of-life notices on the news page.

  • CVE-2025-54988, CVE-2025-66516, CVE-2026-66755 and CVE-2026-66756 in the CVE records (through the CVE Services API that backs cve.org): publication dates, affected packages and ranges, fixed versions and CVSS scores.

  • The 4.0.0 and 4.1.0 notable-changes pages, the 4.1.x migration guide headings and the 3.0.0 change log entries cited.

  • The MigratedIntake and DetectByName programs, compiled and run on Tika 4.1.0 and Java 21 with the outputs shown; that Metadata.CONTENT_TYPE fails to compile; that TikaConfig, ForkParser, LanguageIdentifier and MSOffice are absent from the 4.1.0 class path, and MimeTypes, DefaultDetector, ParsingReader, LinkContentHandler, TeeContentHandler, ToMarkdownContentHandler, AutoDetectReader, LanguageHandler, TikaComponent and LegacyKeyMigrationFilter present.

Not verified:

  • The 1.x and 3.x listings on this page were not compiled or run; they are labelled as such.

  • The listing numbers of Tika in Action were not re-checked against the book; the table cites chapters only.

  • The 2.0 change list rests on the official wiki migration page, not on a 1.x to 2.x run.

  • The jakarta namespace of 3.x rests on the version table of the roadmap page.

  • The Gradle @pom dependency was not built.

  • The Lucene TextField replacement is the current Lucene field API in general; the Lucene version is covered on the search-indexing page.