Text Extraction & Apache Tika
|
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-09. This page was written against Apache POI 5.5.1 (the latest release on that date; the 6.0 line
is in development and has not been released), Java 21 and later, Spring Boot 4.x on Spring Framework 7 and Jakarta EE 11,
and Apache Tika 4.1.0 (the latest release on Maven Central on that date). Many programs do not need to build a
Workbook or an XWPFDocument at all: they only need the text of an uploaded file, for a search index, a preview, a
spam check or a "does this file contain the string we expect" validation. POI ships a family of text extractors for
exactly that. This page explains how they are chosen (ExtractorFactory), what each one does, how to keep memory low,
how to reach embedded documents, and where POI stops and Apache Tika is the better tool. The examples use the Bookshelf
scenario described in the section landing page: books-inventory.xlsx,
books-inventory.xls, loan-letter.docx and loan-letter.doc.
The component map is in Introduction & Architecture, the jars in Setup & Dependencies, and the two container families (OOXML and OLE2) in File Formats: OOXML & OLE2. The extractors sit on top of the same readers described in Spreadsheets with the usermodel API, Streaming Reads: Event and SAX APIs, Word Documents with XWPF and Legacy .doc with HWPF.
What a text extractor is, and when to use one
A text extractor turns a file into a single String (plus, for some formats, a few more targeted accessors) and exposes
the document metadata. It is a read-only convenience layer: it opens the file with the normal POI readers, walks the
content in document order and concatenates the text. Compared with writing the traversal yourself:
-
It is a few lines of code and the same lines work for every supported format.
-
It follows the quirks of each format (shared strings, text boxes, headers and footers, cell comments) so you do not have to.
-
It returns text, not structure. Fonts, colours, number formats, merged regions, table geometry and cell addresses are gone. If you need to know that a value came from cell
C7, use the usermodel or the event API instead.
The official POI position is explicit: for turnkey extraction "including the latest support, styles etc." the project strongly advises Apache Tika, which builds on POI. The POI extractors are the right choice when you want something simple and stand-alone, when you must modify the behaviour heavily, or when you are already inside a POI-only application and one more dependency is not welcome.
| Need | POI extractor | Better tool |
|---|---|---|
Text of one known format, no extra dependencies |
|
- |
Text of an upload whose format is not known in advance |
|
Tika (any format, content-based detection) |
Low-memory text of a large |
|
The event API directly when you need cell addresses (see Streaming Reads) |
Text plus structure (headings, tables, cell values with types) |
Not an extractor job |
The usermodel API ( |
PDF, HTML, email, archives, images with OCR |
Not supported by POI |
Tika |
Rich output (XHTML with structure) for indexing |
Not supported by the POI extractors |
Tika |
The extractor family
All POI extractors implement org.apache.poi.extractor.POITextExtractor, an interface in the poi core jar. In POI 5.x
it is a Closeable; the common surface is small.
| Member | Meaning |
|---|---|
|
The text of the whole document. For many uses this is all you need. |
|
A second extractor whose |
|
The underlying POI object ( |
|
Releases the file. By default closing the extractor also closes the underlying package or file system, so always use try-with-resources. |
Two sub-interfaces add the metadata of the two container families:
-
org.apache.poi.extractor.POIOLE2TextExtractorfor OLE2 files (.xls,.doc,.ppt,.pub,.vsd): addsgetSummaryInformation()andgetDocSummaryInformation()(HPSF). -
org.apache.poi.ooxml.extractor.POIXMLTextExtractorfor OOXML files: addsgetCoreProperties(),getExtendedProperties(),getCustomProperties()andgetPackage().
In POI 4.x these types were abstract classes (POIOLE2TextExtractor and POIXMLTextExtractor in org.apache.poi and
org.apache.poi.ooxml.extractor); in 5.5.1 all three are interfaces. Code written for 4.x that does
extends POITextExtractor has to change to implements. The properties themselves are the
subject of a later page; here they are only a side effect of extraction.
The concrete classes, by format and jar:
| Format | Class | Jar | Notes |
|---|---|---|---|
|
|
|
Loads an |
|
|
|
SAX-based, no workbook object model, lower memory. Has the same option setters. |
|
|
|
Loads an |
|
|
|
Based on the HSSF event user model; low memory, but complex and shared formulas may not be printed correctly. |
|
|
|
Paragraphs, tables, headers, footers, footnotes. |
|
|
|
Paragraph array, header text, footer text, footnotes, endnotes, comments, text boxes. |
|
|
|
Simple text of the older formats. |
|
|
|
Slides, notes, or both. PowerPoint is outside this section’s scope. |
|
|
|
Mentioned for completeness. |
The two Excel extractors for each family implement the small interface org.apache.poi.ss.extractor.ExcelExtractor
(setIncludeSheetNames, setFormulasNotResults, setIncludeHeadersFooters, setIncludeCellComments, getText), so
code that only toggles those options does not care which family it received.
ExtractorFactory: let POI pick the extractor
org.apache.poi.extractor.ExtractorFactory does for extraction what WorkbookFactory does for workbooks. You pass a
File, an InputStream or a POIFSFileSystem; it inspects the first bytes of the content (it does not look at the
file name) with FileMagic, decides whether the stream is OLE2 or OOXML, and returns the matching extractor as a
POITextExtractor.
import java.io.IOException;
import java.nio.file.Path;
import org.apache.poi.EmptyFileException;
import org.apache.poi.extractor.ExtractorFactory;
import org.apache.poi.extractor.POITextExtractor;
public class ExtractAnyFile {
public static void main(String[] args) throws IOException {
for (String name : args) {
Path path = Path.of(name);
try (POITextExtractor extractor = ExtractorFactory.createExtractor(path.toFile())) {
String text = extractor.getText();
System.out.printf("%s -> %s, %d characters%n", path.getFileName(),
extractor.getClass().getSimpleName(), text.length());
System.out.println(text.strip());
System.out.println("--- metadata ---");
System.out.println(extractor.getMetadataTextExtractor().getText().strip());
} catch (EmptyFileException e) {
System.out.println(path.getFileName() + " -> empty file");
} catch (IOException e) {
System.out.println(path.getFileName() + " -> not extractable: " + e.getMessage());
}
System.out.println("==========");
}
}
}
Running it against the Bookshelf files (abridged; the locale of the machine decides the decimal separator, see below):
books-inventory.xlsx -> XSSFExcelExtractor, 90 characters
Books
Bookshelf inventory
Title Price Copies
Dune 12.5 3
Emma 9.9 5
Total copies 8
Empty
--- metadata ---
Created = Fri Oct 09 20:15:37 UTC 2026
Creator = Apache POI
...
books-inventory.xls -> ExcelExtractor, 28 characters
...
loan-letter.docx -> XWPFWordExtractor, 82 characters
Dear Ada Reader,
The book "Dune" is due on 2026-10-30.
Title Due
Dune 2026-10-30
...
loan-letter.doc -> WordExtractor, 54 characters
...
notes.txt -> not extractable: Can't create extractor - unsupported file type: UNKNOWN
Source: POI text extraction and the
ExtractorFactory Javadoc.
Things to know about the factory:
-
Detection is by content, and limited to what POI supports. A file that is neither OLE2 nor OOXML (plain text, PDF, an HTML page renamed
.xlsx) ends in anIOExceptionwith the message "unsupported file type". An empty file throwsEmptyFileException. An OOXML file that is not a Word, Excel or PowerPoint package is rejected as well. -
Providers come from the jars on the class path. The factory delegates to providers:
MainExtractorFactory(in the core jar,.xls),POIXMLExtractorFactory(inpoi-ooxml, OOXML) andOLE2ScratchpadExtractorFactory(inpoi-scratchpad,.doc,.ppt,.pub,.vsd). If a jar is missing, its formats are simply not recognised, and the error message names the providers that were found. Withpoi-scratchpadremoved, the example above reports the.docas "not extractable", and withpoi-ooxmlremoved it does the same for the.xlsx.addProviderandremoveProviderlet an application register its own. -
Return type is the interface. Use
instanceof(pattern matching in Java 21) to reach format-specific methods such asWordExtractor.getHeaderText(). -
Password-protected files. There are overloads taking a password (
createExtractor(File, String),createExtractor(InputStream, String)); an encrypted file without a password fails withEncryptedDocumentException. Encryption is covered by a later page. -
An
OPCPackageoverload no longer exists onExtractorFactoryin 5.5.1. The POI text-extraction page still says the factory accepts "a OOXML Package". In 5.5.1 the overloads areFile,InputStream,POIFSFileSystemandDirectoryNode(plus the password variants). To start from an already opened package, callPOIXMLExtractorFactory.create(OPCPackage)(a class inorg.apache.poi.ooxml.extractor) or construct the specific extractor directly. -
Prefer
FileoverInputStreamwhen the file is on disk: for OOXML, POI can then open the zip lazily from the file instead of buffering the stream, the same advice as forWorkbookFactory.
Excel extraction in detail
Both OOXML Excel extractors write one line per row, with cells separated by a tab, and return the sheets in workbook order. The options are the same:
| Setter | Default | Effect |
|---|---|---|
|
|
Prints the sheet name before its rows. |
|
|
Prints the formula text ( |
|
|
Appends the comment text and its author after the commented cell ( |
|
|
Prints the sheet header and footer (left, centre and right parts). |
|
|
Prints the text of text boxes in the drawing layer. |
|
not set (JVM default) |
Locale used when formatting numbers and dates as the user would see them. |
|
see Javadoc |
Appends the phonetic (furigana) text of Japanese strings. |
Numbers and dates are formatted the way Excel would display them (through DataFormatter, see
Dates, Numbers & DataFormatter), so a date cell comes
out as the text its number format produces rather than as a serial number.
import java.nio.file.Path;
import java.util.Locale;
import org.apache.poi.openxml4j.opc.OPCPackage;
import org.apache.poi.openxml4j.opc.PackageAccess;
import org.apache.poi.xssf.extractor.XSSFEventBasedExcelExtractor;
import org.apache.poi.xssf.extractor.XSSFExcelExtractor;
import org.apache.poi.xssf.usermodel.XSSFWorkbook;
public class ExcelOptions {
public static void main(String[] args) throws Exception {
Path file = Path.of(args[0]);
// DOM-based: loads the whole workbook as XSSFWorkbook
try (XSSFWorkbook workbook = new XSSFWorkbook(OPCPackage.open(file.toFile(), PackageAccess.READ));
XSSFExcelExtractor extractor = new XSSFExcelExtractor(workbook)) {
extractor.setIncludeSheetNames(true);
extractor.setIncludeCellComments(true);
extractor.setIncludeHeadersFooters(true);
extractor.setFormulasNotResults(false);
extractor.setLocale(Locale.ROOT);
System.out.println("[DOM, results]");
System.out.println(extractor.getText());
}
// SAX-based: streams the sheets, no workbook object model
try (OPCPackage pkg = OPCPackage.open(file.toFile(), PackageAccess.READ);
XSSFEventBasedExcelExtractor extractor = new XSSFEventBasedExcelExtractor(pkg)) {
extractor.setIncludeSheetNames(false);
extractor.setFormulasNotResults(true);
System.out.println("[SAX, formulas]");
System.out.println(extractor.getText());
}
}
}
Output for books-inventory.xlsx (a comment on the cell with "Dune", and a SUM formula in the last row):
[DOM, results]
Books
Bookshelf inventory
Title Price Copies
Dune Comment by : Check the second copy 12.5 3
Emma 9.9 5
Total copies 8
Empty
[SAX, formulas]
Bookshelf inventory
Title Price Copies
Dune 12,5 3
Emma 9,9 5
Total copies SUM(C2:C3)
Source: XSSFExcelExtractor and
XSSFEventBasedExcelExtractor.
The output shows several behaviours worth knowing:
-
The comment’s author was empty in this fixture, hence "Comment by :". Comments are off by default.
-
In the DOM run the sheet names are printed (
Books,Empty) and the header text "Bookshelf inventory" is printed under the sheet name; in the SAX run both were requested off or left at their defaults. -
The SAX run used the machine’s default locale (a comma as decimal separator on the machine that produced this output); the DOM run was told
Locale.ROOT. Always set the locale explicitly if the extracted text is compared, indexed or stored. -
The formula result is the cached value stored in the file. POI extractors never recalculate. A workbook written by POI without calling the formula evaluator has no cached result, and the formula cell extracts as
0or as an empty string (see Formulas & Evaluation). UsesetFormulasNotResults(true)to get the formula text instead.
DOM or SAX?
| Aspect | XSSFExcelExtractor |
XSSFEventBasedExcelExtractor |
|---|---|---|
Memory |
Whole workbook object model (several times the size of the XML) |
Streams each sheet; shared strings and styles are still loaded |
Speed |
Slower for large files |
Faster for large files |
Fidelity |
Closest to what the usermodel shows |
Same text for normal cells; fewer special cases |
Typical use |
Small and medium uploads, tools that already have an |
Indexing, validation of large uploads in a server |
The memory behaviour is the one described in Streaming
Reads: Event and SAX APIs: the event extractor avoids the sheet XML tree but still reads the shared string table. For
.xls the equivalent pair is ExcelExtractor and EventBasedExcelExtractor. The POI documentation warns that the
event-based .xls extractor "will have problems correctly outputting more complex formulas" because it sees records one at
a time and has no access to shared and complex formula records.
Choosing the event extractors globally: setThreadPrefersEventExtractors
ExtractorFactory normally returns the DOM extractor for .xlsx. A server that extracts text from untrusted uploads may
prefer the event-based one. Instead of constructing it by hand, you can change the factory’s choice:
| Method | Effect |
|---|---|
|
Affects only the calling thread. Backed by a |
|
Clears the thread-local value for the calling thread (important with pooled threads). |
|
Global override for every thread. |
|
The effective value for the calling thread. |
|
The two settings individually. |
The global value, when not null, wins over the per-thread value. The same static methods exist on
POIXMLExtractorFactory, which is where the choice is applied.
import java.io.IOException;
import java.nio.file.Path;
import org.apache.poi.extractor.ExtractorFactory;
import org.apache.poi.extractor.POITextExtractor;
public class PreferEvent {
static String extract(Path file) throws IOException {
try (POITextExtractor extractor = ExtractorFactory.createExtractor(file.toFile())) {
return extractor.getClass().getSimpleName();
}
}
public static void main(String[] args) throws Exception {
Path xlsx = Path.of(args[0]);
System.out.println("default : " + extract(xlsx));
ExtractorFactory.setThreadPrefersEventExtractors(true);
try {
System.out.println("this thread, event : " + extract(xlsx));
Thread other = new Thread(() -> {
try {
System.out.println("other thread : " + extract(xlsx));
} catch (IOException e) {
throw new RuntimeException(e);
}
});
other.start();
other.join();
} finally {
ExtractorFactory.removeThreadPrefersEventExtractorsSetting();
}
ExtractorFactory.setAllThreadsPreferEventExtractors(true);
try {
Thread other = new Thread(() -> {
try {
System.out.println("all threads, other : " + extract(xlsx));
} catch (IOException e) {
throw new RuntimeException(e);
}
});
other.start();
other.join();
System.out.println("getPreferEventExtractor: " + ExtractorFactory.getPreferEventExtractor());
} finally {
ExtractorFactory.setAllThreadsPreferEventExtractors(null);
}
System.out.println("after reset : " + extract(xlsx));
}
}
Output:
default : XSSFExcelExtractor
this thread, event : XSSFEventBasedExcelExtractor
other thread : XSSFExcelExtractor
all threads, other : XSSFEventBasedExcelExtractor
getPreferEventExtractor: true
after reset : XSSFExcelExtractor
Source: ExtractorFactory Javadoc and
POIXMLExtractorFactory Javadoc.
Two practical consequences. First, because the setting is a thread-local, a request filter that sets it must clear it in
finally, or the next request served by that pooled thread inherits the choice. Second, the preference only affects
the extractor class that ExtractorFactory returns for .xlsx; code that casts the result to XSSFExcelExtractor
will fail with a ClassCastException when the event variant is returned, so program against POITextExtractor or the
ss.extractor.ExcelExtractor interface. Constructing the extractor you want directly is always an alternative that needs
no global state.
Word extraction in detail
XWPFWordExtractor walks the body in document order (paragraphs and tables interleaved, which is why the table text in
the output above comes after the two paragraphs), then the headers and footers, footnotes and endnotes. Table cells are
separated by a tab. setFetchHyperlinks(true) appends the target of each hyperlink after its text, and
setConcatenatePhoneticRuns controls the phonetic text of East Asian runs. WordExtractor for .doc returns the same
information in pieces:
import java.io.IOException;
import java.io.InputStream;
import java.nio.file.Files;
import java.nio.file.Path;
import org.apache.poi.hwpf.HWPFDocument;
import org.apache.poi.hwpf.extractor.WordExtractor;
import org.apache.poi.xwpf.extractor.XWPFWordExtractor;
import org.apache.poi.xwpf.usermodel.XWPFDocument;
public class WordExtraction {
public static void main(String[] args) throws IOException {
Path docx = Path.of(args[0]);
Path doc = Path.of(args[1]);
try (InputStream in = Files.newInputStream(docx);
XWPFDocument document = new XWPFDocument(in);
XWPFWordExtractor extractor = new XWPFWordExtractor(document)) {
extractor.setFetchHyperlinks(true);
System.out.println("[docx]");
System.out.println(extractor.getText());
}
try (InputStream in = Files.newInputStream(doc);
HWPFDocument document = new HWPFDocument(in);
WordExtractor extractor = new WordExtractor(document)) {
System.out.println("[doc]");
for (String paragraph : extractor.getParagraphText()) {
System.out.print(paragraph);
}
System.out.println("header: [" + extractor.getHeaderText().strip() + "]");
System.out.println("footer: [" + extractor.getFooterText().strip() + "]");
}
}
}
Output for loan-letter.docx and loan-letter.doc:
[docx]
Dear Ada Reader,
The book "Dune" is due on 2026-10-30.
Title Due
Dune 2026-10-30
[doc]
Dear Ada Reader,
The book Dune is due on 2026-10-30.
header: []
footer: []
Source: POI Word components,
XWPFWordExtractor and
WordExtractor.
The WordExtractor accessors are getParagraphText() (one array element per paragraph, each ending with the paragraph
mark), getHeaderText(), getFooterText(), getFootnoteText(), getEndnoteText(), getCommentsText(),
getMainTextboxText() and getTextFromPieces() (the raw text of the file’s text pieces, including field codes).
getText() returns the document text as one string. The static WordExtractor.stripFields removes
Word field codes from a string. HWPF is in the orphaned, thread-unsafe poi-scratchpad jar (see
Legacy .doc with HWPF), and .doc extraction is exactly where
untrusted input is most likely to fail in odd ways; treat any RuntimeException from a legacy extractor as "not
extractable" and not as a bug in your code.
What none of the Word extractors give you: formatting, heading levels, list numbering as typed in Word, images or any
layout. They also do not evaluate field results beyond what is stored in the document. For anything structural, read the
document with XWPFDocument (see Word Documents with XWPF).
Embedded documents
Office files can contain other Office files: a Word table whose data lives in an embedded spreadsheet, a worksheet with an embedded Word contract, a PowerPoint with an embedded chart workbook. A search index that skips them misses text, and a malware or data-leak filter that skips them misses payloads. How you reach them depends on the container.
-
OLE2 container (
.xls,.doc,.ppt). The POI documentation shows the pattern: create the outer extractor, then askExtractorFactory.getEmbeddedDocsTextExtractors(POIOLE2TextExtractor)for an array of extractors, one per embedded Excel, Word, PowerPoint or Visio object. The method does not exist for OOXML extractors, and it only returns objects POI can open (an embedded PDF or a picture is ignored). The POI documentation page still shows the old, misspelled namegetEmbededDocsTextExtractors; 5.5.1 has onlygetEmbeddedDocsTextExtractors. -
OOXML container. Embedded files are ordinary package parts, typically under
/xl/embeddings/,/word/embeddings/or/ppt/embeddings/: an embedded.docxor.xlsxis stored as itself, and a non-Office file or an OLE object is stored asoleObject1.bin. Iterate the parts through the package and hand each stream toExtractorFactory.
import java.io.IOException;
import java.io.InputStream;
import java.nio.file.Path;
import java.util.regex.Pattern;
import org.apache.poi.extractor.ExtractorFactory;
import org.apache.poi.extractor.POIOLE2TextExtractor;
import org.apache.poi.extractor.POITextExtractor;
import org.apache.poi.openxml4j.opc.OPCPackage;
import org.apache.poi.openxml4j.opc.PackageAccess;
import org.apache.poi.openxml4j.opc.PackagePart;
public class EmbeddedDocuments {
/** OOXML container: the embedded files are ordinary package parts. */
static void ooxml(Path file) throws Exception {
try (OPCPackage pkg = OPCPackage.open(file.toFile(), PackageAccess.READ)) {
for (PackagePart part : pkg.getPartsByName(Pattern.compile("/(xl|word|ppt)/embeddings/.*"))) {
System.out.println("embedded part " + part.getPartName() + " (" + part.getContentType() + ")");
try (InputStream in = part.getInputStream();
POITextExtractor embedded = ExtractorFactory.createExtractor(in)) {
System.out.println(embedded.getClass().getSimpleName() + ": " + embedded.getText().strip());
} catch (IOException e) {
System.out.println(" not an Office document: " + e.getMessage());
}
}
}
}
/** OLE2 container (.xls, .doc, .ppt): ExtractorFactory finds Excel, Word, PowerPoint and Visio objects. */
static void ole2(Path file) throws IOException {
try (POITextExtractor outer = ExtractorFactory.createExtractor(file.toFile())) {
if (outer instanceof POIOLE2TextExtractor ole2) {
POITextExtractor[] embedded = ExtractorFactory.getEmbeddedDocsTextExtractors(ole2);
for (POITextExtractor extractor : embedded) {
try (extractor) {
System.out.println("embedded " + extractor.getClass().getSimpleName()
+ ": " + extractor.getText().strip());
}
}
System.out.println(embedded.length + " embedded document(s)");
}
}
}
public static void main(String[] args) throws Exception {
ooxml(Path.of(args[0]));
ole2(Path.of(args[1]));
}
}
Run against a books-inventory.xlsx that has a loan-letter.docx embedded (the fixture added the document as a package
part with a relationship) and a books-inventory.xls without embedded objects:
embedded part /xl/embeddings/Microsoft_Word_Document1.docx (application/vnd.openxmlformats-officedocument.wordprocessingml.document)
XWPFWordExtractor: Dear Ada Reader,
The book "Dune" is due on 2026-10-30.
Title Due
Dune 2026-10-30
0 embedded document(s)
The ole2 branch was compiled and run on a file without embedded objects; the file with embedded OLE2 documents was not
available for the test. The code is the pattern from the POI documentation, adapted to the 5.x method name and to
try-with-resources.
Embedded content is also where hostile files hide recursion: an archive whose embedded file contains the same archive. Cap the depth you follow, count the extractors you create, and count the characters you collect.
Memory, performance and limits
| Concern | What to do |
|---|---|
Large |
Use |
Large |
There is no streaming Word extractor. |
Inputs on disk |
Pass a |
Text size |
|
Time |
POI has no timeout. Run extraction in a worker with a deadline and a thread you can abandon, or in a separate process. |
Decimal and date format |
Pass an explicit |
Thread safety |
One extractor per thread; do not share an extractor or its document. |
The limits are described together, with the recommended values, in the Security & Untrusted Files page of this section
and in the official POI security guidance. The most important rule for extraction
is the same as for any other parsing: an uploaded file is hostile input, extraction can throw any Throwable
(including OutOfMemoryError and StackOverflowError), and a server should run it with bounded memory, a deadline and,
for the highest-risk deployments, in a separate process. Since 5.1.0 POI also refuses OOXML parts that contain a DOCTYPE,
which blocks the classic XML external entity attacks at the parser.
Extraction also copies document content into your logs and index. Do not log whole extracted texts, and apply the same access rules to the index as to the original file.
Locale and number formatting
Because the extractors format values like the application does, the same file extracts differently on two machines
with different default locales (12.5 or 12,5; 10/9/2026 or 09.10.2026). Call setLocale where the extractor
offers it, and set the default locale of the extraction worker (Locale.setDefault is global; prefer the setter) so the
text of a file is reproducible. For a search index it is often better to normalise afterwards than to depend on a
display format at all; if you need exact numbers and dates, read the cells with the usermodel API.
Apache Tika
For a complete guide to Tika itself (detection, parsing, metadata, tika-server and Tika Pipes) see the Apache Tika section; this section covers only what a reader of Office files needs.
Apache Tika (https://tika.apache.org/) is the Apache project for detecting and extracting content from many file formats with one API. It is a separate project with its own release cycle, and its Microsoft Office parsers are built on Apache POI. POI’s own documentation recommends it for turnkey extraction. On 2026-10-09 the latest release on Maven Central was 4.1.0 (published 2026-09-29), which requires Java 17; the 3.3.x line remains supported and continues to receive bug fixes. Tika 4.0 is a major release with breaking changes, so check the migration guide before moving an existing integration from the 2.x or 3.x API.
What Tika adds over the POI extractors
| Capability | POI extractors | Tika |
|---|---|---|
Formats |
OOXML and OLE2 Office files (Word, Excel, PowerPoint, Publisher, Visio) |
Hundreds: Office, OpenDocument, PDF, HTML, XML, e-mail, archives, images, audio and video metadata, source code |
Detection |
|
Content-based media type detection (magic bytes, container inspection, file name hint, declared type) with
|
Auto selection of the parser |
|
|
Output |
|
SAX events: plain text, XHTML (with structure) or other handlers; a |
Metadata |
Document properties as text |
Normalised keys ( |
Embedded documents |
OLE2: |
Recursive parsing of embedded files and attachments through a |
Encrypted files |
Password overloads on the factory |
Password via a |
OCR |
No |
Optional through an OCR module (Tesseract) |
Failure isolation |
In-process |
Tika 4 moves parsing into crash-isolated forked processes in |
Dependencies |
POI jars only |
A large dependency tree when the standard parser package is used |
When to use Tika, and when not
Use Tika when:
-
the upload endpoint accepts more than Word and Excel (PDF, HTML, plain text, ODF), or the file type is not trusted and must be detected rather than inferred from the extension. Tika’s detection does not rely on the extension alone;
-
the goal is search indexing (Apache Lucene, Solr, OpenSearch or Elasticsearch ingest pipelines): Tika’s output and metadata keys are the usual input of these systems, and its parsers keep up with new Office variants;
-
you need text from embedded documents without writing the recursion yourself;
-
you need a process boundary:
tika-serverand the 4.x forked parsing exist because parsing of untrusted content is the riskiest part of a document pipeline.
Stay with the POI extractors when:
-
the application already depends on POI and handles only
.xlsx,.docx,.xlsand.doc, and the added dependency weight (Tika’s standard parser package pulls in dozens of artifacts) is not justified; -
you want the text and then the structure of the same file with one open (
getDocument()returns theXWPFDocumentorXSSFWorkbookyou can keep using); -
you need full control over what is extracted and how.
Move to the usermodel or event APIs, not to Tika, when you need values with their types and cell addresses: Tika produces text and metadata, not a spreadsheet model. For a validation of an Excel import, the pipeline described in the Bookshelf import pages reads cells; text extraction is only a pre-check.
Using Tika from Maven
Tika is split into a small core and parser modules. The tika-parsers-standard-package artifact is a POM that pulls the
standard parser modules (Microsoft, PDF, text, HTML, XML, packaging and more); it has packaging pom, so declare it with
<type>pom</type>. In a project that already declares POI, keep a single POI version: Tika’s Microsoft module brings
its own POI dependency, and the rule from Setup & Dependencies
(never mix POI jars of different versions) still applies. Check with mvn dependency:tree that every org.apache.poi
artifact has the same version.
<dependency>
<groupId>org.apache.tika</groupId>
<artifactId>tika-core</artifactId>
<version>4.1.0</version>
</dependency>
<dependency>
<groupId>org.apache.tika</groupId>
<artifactId>tika-parsers-standard-package</artifactId>
<version>4.1.0</version>
<type>pom</type>
</dependency>
Source: Apache Tika 4.1.0 and Tika downloads.
Detection and extraction with Tika
The Tika facade class detects the type and returns the text in one call; AutoDetectParser gives control over the
handler and the metadata. In Tika 4.x the parser methods take a TikaInputStream (not a plain InputStream), which is
Tika’s own stream wrapper that can spool to disk and reopen the content for detection and parsing.
import java.nio.file.Path;
import org.apache.tika.Tika;
import org.apache.tika.io.TikaInputStream;
import org.apache.tika.metadata.HttpHeaders;
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 TikaExtraction {
public static void main(String[] args) throws Exception {
Path file = Path.of(args[0]);
// Facade: detection and plain-text extraction in two calls
Tika tika = new Tika();
System.out.println("detected: " + tika.detect(file));
tika.setMaxStringLength(100_000);
System.out.println(tika.parseToString(file).strip());
// Parser API: text and metadata, with a write limit against huge documents
AutoDetectParser parser = new AutoDetectParser();
BodyContentHandler handler = new BodyContentHandler(100_000);
Metadata metadata = new Metadata();
try (TikaInputStream in = TikaInputStream.get(file)) {
parser.parse(in, handler, metadata, new ParseContext());
}
System.out.println("Content-Type: " + metadata.get(HttpHeaders.CONTENT_TYPE));
System.out.println("title: " + metadata.get(TikaCoreProperties.TITLE));
System.out.println(handler.toString().strip());
}
}
Output for loan-letter.doc (the .xlsx run printed the same detection of the spreadsheet media type, followed by the
sheets, the comment, and the text of the embedded loan-letter.docx without any extra code):
detected: application/msword
Dear Ada Reader,
The book Dune is due on 2026-10-30.
Content-Type: application/msword
title: null
Dear Ada Reader,
The book Dune is due on 2026-10-30.
Source: Apache Tika 4.1.0 API (Javadoc) and the Tika home page.
Points that matter in production:
-
BodyContentHandler(100_000)andTika.setMaxStringLengthcap the extracted characters; the limit in the example is an arbitrary value, so choose one that fits the largest document you are willing to index. Always set an explicit cap for uploads. -
The facade creates a parser (and loads its configuration) per
Tikainstance. Create oneTikaorAutoDetectParserand reuse it; parsers are thread-safe, handlers andMetadataobjects are not. -
Tika detects the type from content, then parses. A file with an
.xlsxextension but PDF content is parsed as a PDF. If your application accepts only Excel, check the detected media type against an allow-list (see the formats table in File Formats: OOXML & OLE2) before accepting the file. -
Treat Tika like any other parser of untrusted data. The 4.x line recommends and supports out-of-process parsing for that reason. A parser crash, an endless loop or an out-of-memory error should not take the application down.
Summary
-
All POI text extractors implement
POITextExtractor(getText,getMetadataTextExtractor,close); OLE2 extractors add HPSF metadata, OOXML extractors add the package properties. -
ExtractorFactory.createExtractorpicks the extractor from the content, using the providers of the jars on the class path:poifor.xls,poi-ooxmlfor OOXML,poi-scratchpadfor.docand the other legacy formats. -
Excel:
XSSFExcelExtractor(DOM) orXSSFEventBasedExcelExtractor(SAX, low memory) for.xlsx;ExcelExtractororEventBasedExcelExtractorfor.xls. Formula results are the cached values; set theLocaleexplicitly. -
setThreadPrefersEventExtractorsandsetAllThreadsPreferEventExtractorsmake the factory return the event-based.xlsxextractor; remove the thread setting infinally. -
Word:
XWPFWordExtractorfor.docx;WordExtractorfor.doc. Text only, no structure, no layout. -
Embedded documents:
getEmbeddedDocsTextExtractorsfor OLE2 containers, package parts underembeddings/for OOXML. -
Treat extraction as parsing of hostile input: size, time and memory limits, a worker you can abandon, a separate process when the risk is high.
-
For many formats, content-based detection, search indexing, recursive embedded parsing and process isolation, use Apache Tika, which builds on POI. For typed cell values and structure, use the usermodel or event APIs instead.
References
-
POI text extraction: https://poi.apache.org/text-extraction.html
-
Secure processing: https://poi.apache.org/security.html
-
Related projects: https://poi.apache.org/related-projects.html
-
Javadoc,
ExtractorFactory: https://poi.apache.org/apidocs/dev/org/apache/poi/extractor/ExtractorFactory.html -
Javadoc,
POITextExtractor: https://poi.apache.org/apidocs/dev/org/apache/poi/extractor/POITextExtractor.html -
Javadoc,
POIOLE2TextExtractor: https://poi.apache.org/apidocs/dev/org/apache/poi/extractor/POIOLE2TextExtractor.html -
Javadoc,
POIXMLTextExtractor: https://poi.apache.org/apidocs/dev/org/apache/poi/ooxml/extractor/POIXMLTextExtractor.html -
Javadoc,
POIXMLExtractorFactory: https://poi.apache.org/apidocs/dev/org/apache/poi/ooxml/extractor/POIXMLExtractorFactory.html -
Javadoc,
XSSFExcelExtractor: https://poi.apache.org/apidocs/dev/org/apache/poi/xssf/extractor/XSSFExcelExtractor.html -
Javadoc,
XSSFEventBasedExcelExtractor: https://poi.apache.org/apidocs/dev/org/apache/poi/xssf/extractor/XSSFEventBasedExcelExtractor.html -
Javadoc,
ExcelExtractor(HSSF): https://poi.apache.org/apidocs/dev/org/apache/poi/hssf/extractor/ExcelExtractor.html -
Javadoc,
EventBasedExcelExtractor: https://poi.apache.org/apidocs/dev/org/apache/poi/hssf/extractor/EventBasedExcelExtractor.html -
Javadoc,
XWPFWordExtractor: https://poi.apache.org/apidocs/dev/org/apache/poi/xwpf/extractor/XWPFWordExtractor.html -
Javadoc,
WordExtractor: https://poi.apache.org/apidocs/dev/org/apache/poi/hwpf/extractor/WordExtractor.html -
Javadoc,
Word6Extractor: https://poi.apache.org/apidocs/dev/org/apache/poi/hwpf/extractor/Word6Extractor.html -
Javadoc,
ZipSecureFile: https://poi.apache.org/apidocs/dev/org/apache/poi/openxml4j/util/ZipSecureFile.html -
Javadoc,
OPCPackage: https://poi.apache.org/apidocs/dev/org/apache/poi/openxml4j/opc/OPCPackage.html -
Apache Tika: https://tika.apache.org/
-
Apache Tika 4.1.0: https://tika.apache.org/4.1.0/index.html
-
Apache Tika 4.1.0 API: https://tika.apache.org/4.1.0/api/
-
Tika migration to 4.x: https://tika.apache.org/docs/4.1.x/migration-to-4x/index.html
-
Tika downloads: https://tika.apache.org/download.html