Local override: $TIKA_SKILLS_LOCAL/development/LOCAL.md (default ~/.tika-skills),
read after this file, wins on conflict.
Tika Development Skill
Guidelines and checklist for developing against the Apache Tika codebase.
Question the direction, not just the code
Before optimizing a change — yours or a PR's — ask whether it should exist: does it belong in Tika, is the complexity proportional to the need, would config, an existing mechanism, a plugin, or documentation serve the use case more cheaply? Every merged feature is surface the project maintains for decades. Steelman the use case first and question the vehicle, not the goal; pushback must name a concrete cost or a simpler path — never taste alone, and never no for the sake of no. "The direction is right" is a valid conclusion.
Feature whose shape isn't known yet: spike first, cut PRs after
(.skills/devs/feature-workflow/SKILL.md).
Git Policy (default — personally overridable)
Never run git commit or git push — no commits of any kind, including
merge commits. If a merge is needed, use git merge --no-commit --no-ff
and hand back. Stage files and provide the suggested commit message for
the user to run.
Never write to GitHub (PR comments, reviews, issues, labels, merges).
Read-only gh is fine.
Precedence: these are conservative defaults for workflow — actions on
the contributor's own machine and accounts. A contributor's personal agent
configuration (their own skills, CLAUDE.md/AGENTS.md, settings, or a
LOCAL.md overlay — see AGENTS.md) may override them. Everything else in
this file — code and comment conventions, test discipline, hygiene,
pre-commit checks — governs what lands in the repo and is project policy:
personal configuration does not override it.
Session Start Checklist
Local Maven repo — Default to an in-repo
.local_m2_repo(via-Dmaven.repo.local=$(pwd)/.local_m2_repo) unless the user says otherwise. This isolates builds from the shared~/.m2/repositoryand avoids polluting or being affected by other projects.Maven wrapper — Use
./mvnw; fall back to a system Maven (3.9+) only if the wrapper is absent.Merge conflicts — Check
git statusforUUfiles and resolve before building.
Maven Rules
Always include
cleanin every./mvnwinvocation. Stale classes intarget/cause hard-to-debug failures../mvnw clean compile -pl <module> ... # not just: mvnw compile ./mvnw clean test -pl <module> ... # not just: mvnw test ./mvnw clean install -pl <module> ... # not just: mvnw installAlways use absolute path for local repo:
-Dmaven.repo.local=$(pwd)/.local_m2_repoFast builds with
-Pfast— use thefastprofile to skip tests, checkstyle, spotless, and rat in one flag. Prefer this over individual-Dskip flags when you want a quick build (e.g., installing for downstream consumers or eval runs):./mvnw clean install -pl <module> -am -Pfast \ -Dmaven.repo.local=$(pwd)/.local_m2_repoRun without
-Pfastbefore final commit to catch formatting and style issues. License (rat) checks run only under-Ppedantic(or explicitapache-rat:check), not in default builds.-Pfastskips test execution (by design): a green-Pfastbuild — including-Pfast test— has run zero tests, and staletarget/surefire-reports/*will look current. Verify with a plain (non--Pfast)testrun.Plugin zips resolve only after
package— a reactorclean testfails on modules that depend on pipes plugin zips (tika-server-core,tika-app, ...) unless the zips are already in the local repo. Useclean install(or-Pfast installfirst).Forked JVM tests — Integration tests in
tika-pipesfork new JVMs that load classes from the local Maven repo, not fromtarget/classes. You must./mvnw clean install -Pfastthe changed modules before running integration tests that fork.
Building Specific Modules
# Single module (with dependencies)
./mvnw clean compile -pl <module> -am \
-Dmaven.repo.local=$(pwd)/.local_m2_repo
# Run a single test class
./mvnw clean test -pl <module> -Dtest=<TestClass> \
-Dmaven.repo.local=$(pwd)/.local_m2_repo -Dcheckstyle.skip=true
# Install for downstream consumers (tika-app, integration tests)
./mvnw clean install -pl <module> -am -Pfast \
-Dmaven.repo.local=$(pwd)/.local_m2_repo
Common Module Paths
| Module | Path |
|---|---|
| tika-core | tika-core |
| tika-app | tika-app |
| tika-server | tika-server/tika-server-core |
| tika-eval | tika-eval/tika-eval-app |
| Pipes core | tika-pipes/tika-pipes-core |
| Pipes API | tika-pipes/tika-pipes-api |
| Async CLI | tika-pipes/tika-async-cli |
Code Conventions
- ASF License 2.0 header on all Java files
- Spotless formatter runs during build — don't fight it
- Tests use
@TempDir Path tmpfor temp directories - No emojis in code or comments
- Comments: every comment must earn its place — one short line by default; multi-line only for a genuinely non-obvious WHY (subtle invariant, workaround, spec quirk). Never restate the code, narrate the next line, justify the change to a reviewer, or describe past states of the code.
- Input files are hostile: bound anything derived from document content (loop counts, allocations, timeouts); release external processes, temp files, and pool slots on every failure path.
- No local/machine-specific paths in committed code, tests, docs, or
config — never
/home/<user>,/Users/<user>,C:\Users\<user>, or a personal~/data/.... Use a placeholder (<workdir>/,<corpus>),@TempDir, or an in-reposrc/test/resourcesfixture instead. Only legitimate exception: a path that is the data under test (e.g. an expected metadata value extracted from a test document) — leave those untouched.
Test Discipline
- A behavioral change gets a regression test that fails without it. Where impractical (timing, native binaries, external services, kill paths), say so explicitly and name the next-best check.
- Prove a negative by reverting the fix: an "X does not happen" test that
still passes is not a test. Assert on what the consumer is handed, not an
ambient side effect (a
@TempDirwatch missesTemporaryResourcesnot bound to it). - Cover error paths and the configuration/mode matrix — a behavior verified in only one parse mode or config shape is a gap (RMETA-only tests miss CONCATENATE-only bugs).
- Keep tests non-duplicative: don't add a test whose failure another test already guarantees.
- Where there's bang for the buck, prefer parameterized tests over copy-pasted cases, randomized inputs over hand-picked ones (log the seed so failures reproduce), and fuzzing for parsers and format/boundary arithmetic. Don't force it on code a couple of fixed cases fully cover.
Metadata Keys & Schema Registry
Adding/renaming a metadata key touches the committed, build-gated registry in
tika-metadata-schema — regeneration has real traps. See
.skills/devs/metadata-schema/SKILL.md.
Testing an End-to-End Change
When a change affects parsing output (e.g., new parser behavior,
encoding fix), run a before/after comparison using tika-eval.
See .skills/devs/tika-eval-compare/SKILL.md for the full procedure.
Pre-Commit Checks
# Full compile with checkstyle (catches formatting issues)
./mvnw clean compile -pl <module> -am \
-Dmaven.repo.local=$(pwd)/.local_m2_repo
# Run module tests
./mvnw clean test -pl <module> \
-Dmaven.repo.local=$(pwd)/.local_m2_repo
Also before commit:
- Commit message / PR title references a JIRA ticket (
TIKA-XXXX). - CHANGES entry for user-visible changes.
- New dependencies: ASF-compatible license; LICENSE/NOTICE updated.
Scan the staged diff for machine-specific local paths before committing (see Code Conventions). Added lines only; review any hit by hand — a test fixture's expected value is allowed, a real config/doc/code path is not:
git diff --cached -U0 | grep -E '^\+' \
| grep -nE '/home/[A-Za-z0-9._-]+|/Users/[A-Za-z0-9._-]+|[A-Za-z]:\\+Users|~/data/' \
&& echo "^ local path in staged diff — replace with a placeholder/fixture"