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 UnpackConfig programmatically per request from Java code by calling parseContext.set(UnpackConfig.class, …​) on the ParseContext attached to your FetchEmitTuple. The JSON parse-context section above is the declarative equivalent.

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

emitter

String

(from FetchEmitTuple)

Emitter name for embedded bytes. Falls back to the FetchEmitTuple’s emitterId.

maxUnpackBytes

long

10 GiB

Maximum total bytes to extract per file. Set to -1 for unlimited (not recommended). 0 is not unlimited — it means zero bytes, so the first embedded file’s extraction is immediately capped.

includeOriginal

boolean

false

Include the container document itself in the output.

zipEmbeddedFiles

boolean

false

REGULAR only: collect all embedded files into a single ZIP archive, emitted at the container’s emit key plus -embedded.zip. Frictionless packaging is outputMode.

includeMetadata

boolean

unset: on for FRICTIONLESS, off for REGULAR

Metadata for every extracted file, in either format: one rmeta.json under Frictionless (a data package without the parse is half a package, so it is on unless you set false), a .metadata.json sidecar per file under REGULAR zip output (opt-in; tika-server’s /unpack/all sets it). REGULAR loose output (no zip) writes none.

includeMetadataInZip

boolean

 — 

Deprecated alias for includeMetadata (removal in 5.0). Still accepted so 4.0.0 configs load.

zeroPadName

int

0

Zero-pad embedded IDs in output names (e.g., 8 produces 00000001).

suffixStrategy

NONE, EXISTING, DETECTED

NONE

How to determine file extensions for extracted files. See Suffix Strategies.

embeddedIdPrefix

String

"-"

Separator between emitKeyBase and the embedded ID. Read only when keyBaseStrategy=CUSTOM; the DEFAULT strategy uses a fixed -embed/ separator instead.

keyBaseStrategy

DEFAULT, CUSTOM

DEFAULT

Strategy for generating emit keys. See Key Base Strategies.

emitKeyBase

String

""

Custom base path when keyBaseStrategy=CUSTOM.

outputFormat

FRICTIONLESS, REGULAR

FRICTIONLESS

FRICTIONLESS is a Frictionless Data Package; REGULAR is the pre-4.2 flat layout (files at the root, no manifest).

outputMode

ZIPPED, DIRECTORY

ZIPPED

ZIPPED packages everything into one archive; DIRECTORY emits each extracted file to the emitter as its own item.

packageRoot

String

(container’s emit key)

FRICTIONLESS + DIRECTORY only: emit key prefix of the package. Unset, each container gets <emitKey>/datapackage.json, <emitKey>/unpacked/…​; "" writes the package at the emitter’s root (tika-app does this for a single input file).

emptyPackage

WRITE, SKIP

WRITE

FRICTIONLESS only: a document with nothing to unpack still gets a package (datapackage.json and rmeta.json), or with SKIP none — and tika-server’s /unpack answers 204. The thumbnail preset sets SKIP.

packageName

String

(fetch key’s file name)

FRICTIONLESS only: the name in datapackage.json; blank leaves it out. tika-server sets the caller’s filename, or blank when there is none.

includeFullMetadata

boolean

 — 

Deprecated alias for includeMetadata (removal in 5.0). Still accepted so 4.0.0 configs load.

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; embeddedIdPrefix is 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; outputFormat: REGULAR in the server config for the flat zip

Frictionless; --unpack-format=REGULAR

outputFormat

packaging

always one zip (ZIPPED is pinned; an HTTP response is one body)

loose files (DIRECTORY); --unpack-mode=ZIPPED for one zip

outputMode governs Frictionless, zipEmbeddedFiles governs REGULAR

depth

embedded-limits in config

-z = direct attachments, -Z = recursive

embedded-limits

metadata

Frictionless: by default; REGULAR: /unpack/all

Frictionless: by default; REGULAR zip: --unpack-include-metadata

includeMetadata (unset: on for Frictionless, off for REGULAR)

source document’s bytes

/unpack/all

only via -c (unpack-config.includeOriginal)

includeOriginal

handler for tk:content

/unpack/all/{handlerType} or the config part

--handler

content-handler-factory

/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.json as "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’s tk:embedded-id (0 is the source document, under includeOriginal); it matches the file’s row in rmeta.json if 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.