A Spring Boot 3 / Java 21 backend service that uses the Anthropic API to extract structured requirements from job descriptions and produce a gap analysis against a resume.
Status: v0.1 complete — all four roadmap phases shipped. Exploring v0.2, May 2026.
Job descriptions are dense, inconsistent, and full of buried signals — some "requirements" are real, others are wishlist, and the distinction matters when deciding whether to apply, how to tailor a resume, or how to prepare for an interview. Doing this comparison manually for every JD is slow and not consistent across attempts.
jd-match treats this as a structured-extraction problem: given a JD and a resume, get back a typed, comparable representation of what the role wants and where the candidate matches or doesn't. The Anthropic API does the reading; the service does the orchestration, persistence, and consistency.
It's also a focused exploration of production patterns for AI-assisted backend services — structured outputs over free text, prompt versioning, observability around model calls, and idempotent caching of expensive generations.
┌────────────────────────────────────────────┐
│ REST API (Spring Boot 3, Java 21) │
│ POST /analyses GET /analyses/{id} │
└─────────────────────┬──────────────────────┘
│
┌─────────────┼─────────────┐
▼ ▼ ▼
Resume Service JD Service Analysis Service
│
▼
Anthropic API
(structured outputs)
│
▼
Postgres (pgvector-ready)
Java 21 · Spring Boot 3 · Postgres · Docker · Anthropic Java SDK · AWS
- Phase 1 — Core REST API, JD requirement extraction, resume storage, gap analysis pipeline
- Phase 2 — MCP server wrapper so Claude Code and Claude Desktop can use jd-match as a tool
- Phase 3 — RAG layer over a corpus of past JDs for pattern recognition across similar roles (foundation: embedding + similarity search)
- Phase 4 — AWS deployment and a write-up of the design decisions and tradeoffs
# Postgres
docker-compose up -d
# Service
./mvnw spring-boot:run
# Tests (a Postgres integration test runs via Testcontainers, so Docker must be running)
./mvnw testSet your ANTHROPIC_API_KEY in .env before running the service (see .env.example); it is loaded automatically at startup.
The running service also speaks the Model Context Protocol over Streamable HTTP at
/mcp, so MCP clients can call it as a tool. Start the service, then register it:
# Claude Code
claude mcp add --transport http jd-match http://localhost:8080/mcpFor Claude Desktop, add a custom connector pointing at http://localhost:8080/mcp
(Settings → Connectors → Add custom connector).
analyze_fit(jdText, resumeText)— returns a structured gap analysis (overall fit score, per-requirement assessments with evidence, strengths, and gaps). Backed by the same pipeline asPOST /analyses, so results are cached and idempotent.extract_jd_requirements(jdText)— returns the structured requirements for a JD (role title, seniority, categorized requirements). Cached per JD.get_analysis(analysisId)— fetches a previously created analysis by id.find_similar_jds(jdText, topK?)— returns previously analyzed JDs most similar to the given text, ranked by embedding similarity. Same search asPOST /jds/similar.
Every JD extracted during an analysis is embedded (locally, via all-MiniLM-L6-v2 — no
API key) and stored in Postgres with pgvector. POST /jds/similar {jdText, topK?}
(and the find_similar_jds MCP tool) returns the closest past JDs by cosine distance.
This needs the pgvector extension, so local Postgres runs the pgvector/pgvector:pg16
image (see docker-compose.yml); AWS RDS supports it as an extension. POST /jds/reindex
re-embeds every stored JD — a backfill for JDs added before similarity search existed.
A container image and Terraform for AWS App Runner + RDS Postgres (pgvector) live under
deploy/terraform/. Build it with docker build . — the
image bundles the embedding model and native libs so it starts without downloading anything
(ai.djl.offline=true). App Runner health-checks /actuator/health/readiness.
A few decisions worth calling out (full write-up in DESIGN.md):
- Structured outputs over free-form generation. Every call to Claude returns JSON validated against a Java record. This makes the AI a reliable component of a larger system rather than a chatty wrapper.
- Prompts as resources, not strings. Prompts live in
src/main/resources/prompts/so they can be versioned, diff-reviewed, and swapped without touching code. - Postgres from day one. Sets up for vector search in Phase 3 without a migration.
- Idempotent analyses. Re-running the same JD and resume returns the cached analysis. Cheap and predictable, and a useful guardrail when iterating on prompts.
MIT