unpack-config: Extracting Embedded Document Bytes
When processing container files (ZIP, DOCX, PDF with attachments, etc.), you may want to
extract the raw bytes of embedded documents in addition to parsing them. The
unpack-config component (Java: UnpackConfig) controls how embedded bytes are
extracted and emitted.
Quick Start
To turn on byte extraction for every document the pipeline processes, set
parseMode to UNPACK in the pipes section of your tika-config.json.
That’s the minimum configuration — extraction defaults are fine for most cases.
{
"pipes": {
"parseMode": "UNPACK"
}
}
To tune extraction (size limits, naming, ZIP output, etc.), add an unpack-config
block under the top-level parse-context section. All the options listed below
live inside that block:
{
"pipes": {
"parseMode": "UNPACK"
},
"parse-context": {
"unpack-config": {
"maxUnpackBytes": 104857600,
"outputMode": "ZIPPED"
}
}
}
This extracts both metadata (like RMETA mode) and embedded document bytes. Since 4.2 the
output is a Frictionless Data Package on every surface,
one <key>-frictionless.zip per container unless outputMode is DIRECTORY;
"outputFormat": "REGULAR" restores the pre-4.2 flat layout.
|
You can also set |
Configuration Options
All options below are fields of the unpack-config block — nest them inside
parse-context.unpack-config as shown in the Quick Start.
| Property | Type | Default | Description |
|---|---|---|---|
|
String |
(from FetchEmitTuple) |
Emitter name for embedded bytes. Falls back to the FetchEmitTuple’s emitterId. |
|
long |
10 GiB |
Maximum total bytes to extract per file. Set to |
|
boolean |
|
Include the container document itself in the output. |
|
boolean |
|
|
|
boolean |
unset: on for |
Metadata for every extracted file, in either format: one |
|
boolean |
— |
Deprecated alias for |
|
int |
|
Zero-pad embedded IDs in output names (e.g., |
|
NONE, EXISTING, DETECTED |
|
How to determine file extensions for extracted files. See Suffix Strategies. |
|
String |
|
Separator between |
|
DEFAULT, CUSTOM |
|
Strategy for generating emit keys. See Key Base Strategies. |
|
String |
|
Custom base path when |
|
FRICTIONLESS, REGULAR |
|
|
|
ZIPPED, DIRECTORY |
|
|
|
String |
(container’s emit key) |
|
|
WRITE, SKIP |
|
|
|
String |
(fetch key’s file name) |
|
|
boolean |
— |
Deprecated alias for |
Examples
One package per container, with the original
{
"pipes": {
"parseMode": "UNPACK"
},
"parse-context": {
"unpack-config": {
"outputMode": "ZIPPED",
"includeOriginal": true
}
}
}
The pre-4.2 flat ZIP with sidecars
{
"pipes": {
"parseMode": "UNPACK"
},
"parse-context": {
"unpack-config": {
"outputFormat": "REGULAR",
"zipEmbeddedFiles": true,
"includeMetadata": true,
"includeOriginal": true
}
}
}
Custom Naming
embeddedIdPrefix only applies to keyBaseStrategy=CUSTOM, so set both:
{
"pipes": {
"parseMode": "UNPACK"
},
"parse-context": {
"unpack-config": {
"zeroPadName": 8,
"suffixStrategy": "DETECTED",
"keyBaseStrategy": "CUSTOM",
"emitKeyBase": "document",
"embeddedIdPrefix": "-embed-"
}
}
}
Produces names like document-embed-00000001.pdf. Under the DEFAULT strategy the same
zeroPadName/suffixStrategy settings would produce <containerKey>-embed/00000001.pdf.
These keys are REGULAR output’s; a Frictionless package sits at <containerKey>/ (or
<containerKey>-frictionless.zip) and names its files unpacked/<id><suffix>, so only
zeroPadName and suffixStrategy show there.
Suffix Strategies
NONE-
No file extension added to extracted files.
EXISTING-
Use the file extension from the embedded document’s resource name.
DETECTED-
Use the file extension based on the detected MIME type.
Key Base Strategies
DEFAULT-
Output key is
{containerKey}-embed/{id}{suffix}. The-embed/separator is fixed;embeddedIdPrefixis not consulted. CUSTOM-
Output key is
{emitKeyBase}{embeddedIdPrefix}{id}{suffix}.
Safety Limits
maxUnpackBytes bounds zip bombs and other files that expand to enormous sizes. The 10 GiB
default suits most corpora; lower it for untrusted input.
Hitting the limit is silent apart from a log line. The embedded file being written is
truncated at the remaining budget, each later embedded file is skipped, and each case
logs a WARN — but nothing is stamped on the metadata and the result status is unchanged
(PARSE_SUCCESS / EMIT_SUCCESS). Watch the log, not the status, for truncation.
maxUnpackBytes: -1 (or any negative value) disables the limit — not recommended for
untrusted input. 0 is not "unlimited": it caps extraction at zero bytes.
Frictionless Data Package Output
The UNPACK mode can output files in Frictionless Data Package format,
a standard for packaging data files with their metadata. This format includes a datapackage.json
manifest with file checksums and MIME types, making it easy to verify and process extracted files.
It is the default since 4.2 (TIKA-4681); 4.0 and 4.1 emitted the flat REGULAR layout
unless outputFormat said otherwise. To get that layout back:
{
"pipes": {
"parseMode": "UNPACK"
},
"parse-context": {
"unpack-config": {
"outputFormat": "REGULAR"
}
}
}
Output Structure
A zipped package contains:
output.zip
├── datapackage.json # Manifest with file list, SHA256 hashes, mimetypes
├── rmeta.json # Full RMETA metadata (unless includeMetadata=false)
└── unpacked/
├── 1.pdf
├── 2.png
└── ...
The tk:content field inside rmeta.json (and inside the per-file .metadata.json
sidecars includeMetadata adds to REGULAR zip output) is Markdown by default. On tika-server
change it with /unpack/all/{handlerType} or a content-handler-factory in the config
part; in a config, the factory alone. Each entry records the handler it was written with in
tk:content-handler-type:
{
"unpack-config": { "outputFormat": "FRICTIONLESS" },
"basic-content-handler-factory": { "type": "XML" }
}
To get metadata with no extracted text at all — one sidecar per file, but no tk:content — select the IGNORE handler. On the test corpus below this drops the sidecars from 20,257 to
11,132 bytes and is the way to avoid shipping every embedded document’s text alongside its
bytes:
{
"unpack-config": { "includeMetadata": true },
"basic-content-handler-factory": { "type": "IGNORE" }
}
On tika-server the handler is also a path segment on /unpack/all/{handlerType}, which
needs no config part and so no allowPerRequestConfig. Plain /unpack carries no
metadata for a handler to render, so it takes no segment.
curl -T container.docx http://localhost:9998/unpack/all/ignore # files + metadata, no text
Naming the handler in both the path and a config part is a 400, as on /tika and
/rmeta.
What each surface produces
The five knobs interact differently per surface. Two defaults changed since 4.0.0: a
Frictionless package carries rmeta.json unless includeMetadata is false (4.0.0 left
it out unless includeFullMetadata was true), and since 4.2 Frictionless is the format
on every surface. REGULAR’s layout is as it was; since 4.2 each .metadata.json sidecar is
the file’s row from the metadata list, so it gets the metadata filters and text inference as
rmeta.json does, and a row a filter removes gets no sidecar. Before 4.2 the package’s rmeta.json was
named metadata.json, was not listed in datapackage.json, and did not get the metadata
filters.
A document with nothing to unpack is still a package: datapackage.json and rmeta.json,
with rmeta.json its only resource and no unpacked/. /unpack returns it with 200
rather than the 204 it gave before 4.2 (REGULAR output still gives 204). To get the 204
back, send "unpack-config": {"emptyPackage": "SKIP"} in the request’s config part.
tika-server /unpack |
tika-app -z / -Z |
pipes JSON config | |
|---|---|---|---|
format |
Frictionless; |
Frictionless; |
|
packaging |
always one zip ( |
loose files ( |
|
depth |
|
|
|
metadata |
Frictionless: by default; REGULAR: |
Frictionless: by default; REGULAR zip: |
|
source document’s bytes |
|
only via |
|
handler for |
|
|
|
/unpack/all means the source document’s bytes plus metadata in both formats. Under REGULAR
that is where the per-file sidecars come from; under Frictionless rmeta.json is already
there, so /all adds only the source document. outputMode: DIRECTORY is refused on
/unpack: an HTTP response is one zip, so a request asking for it gets a 400, and a server
whose own unpack-config says so fails at startup if the unpack endpoint is enabled — use
/pipes or /async for per-file emission.
Output trees for doc.docx with three attachments:
tika-server (one zip) tika-app -Z doc.docx out (loose)
datapackage.json out/datapackage.json
rmeta.json out/rmeta.json
unpacked/1.emf ... out/unpacked/1.emf ...
+ with /all: unpacked/0.docx
tika-app -Z <dir> out (loose)
out/doc.docx/datapackage.json
out/doc.docx/rmeta.json
out/doc.docx/unpacked/1.emf ...
REGULAR, tika-server (one zip) REGULAR, tika-app -Z --unpack-format=REGULAR
1.emf out/doc.docx.json
2.zip out/doc.docx-embed/1.emf
3.bin out/doc.docx-embed/2.zip
+ with /all: 0.docx and one out/doc.docx-embed/3.bin
<name>.metadata.json per file
For a single input file the output directory is the package; with no output directory
tika-app writes to doc.docx-unpacked/ (REGULAR output too). For a directory of inputs each
gets its own package under its relative path.
The package’s rmeta.json is the parse’s metadata list. On any surface, when the package
goes to the container’s own emitter no separate <emitKey>.json is written; with
includeMetadata: false the package is the bytes and the manifest, and no metadata is
written at all. With emptyPackage: SKIP, a document with nothing to unpack writes nothing.
A package sent to a different emitter than the container’s leaves the container’s metadata
emit as it was.
The datapackage.json file contains:
-
List of all extracted files and
rmeta.jsonas "resources" -
SHA256 hash for each file
-
MIME type for each file
-
File size in bytes
-
name: the Frictionless resource name, derived from the path (unpacked/3.pdf→unpacked-3.pdf) -
tk:embedded-id: the extracted file’stk:embedded-id(0is the source document, underincludeOriginal); it matches the file’s row inrmeta.jsonif there is one. The rows are not in id order; match on the id. -
tk:resource-name: the file’s original name, when it had one
The package passes frictionless validate:
{
"name" : "doc.docx",
"resources" : [
{ "name" : "rmeta.json", "path" : "rmeta.json", "mediatype" : "application/json", ... },
{ "name" : "unpacked-3.pdf", "path" : "unpacked/3.pdf", "mediatype" : "application/pdf",
"bytes" : 109786, "hash" : "sha256:115d...", "tk:embedded-id" : 3,
"tk:resource-name" : "report.pdf" }
]
}
One thing the validator reports that is not a manifest problem: it decompresses resources
whose extension says .zip or .gz, so a corrupt or truncated embedded archive shows up
as a compression-error. The manifest is still valid; the file is what it is.
CLI Usage
The -Z flag turns on recursive unpack (the Pipes-mode counterpart of standard-mode -z),
and -i/-o are the Pipes input/output directories:
java -jar tika-app.jar -Z -i /path/to/input -o /path/to/output
-i expects a directory of containers to unpack, not a single file. For
one-off unpacking of a single document, see the standard-mode -z/--extract
flag — though as of 4.x that path also routes through the Pipes machinery and
expects an input directory.
|
Code Examples
For working code examples, see:
-
tika-pipes/tika-pipes-integration-tests/src/test/java/org/apache/tika/pipes/core/UnpackModeTest.java -
tika-server/tika-server-standard/src/test/java/org/apache/tika/server/standard/TikaPipesTest.java
These test files demonstrate all configuration options with assertions.