Java resume parser: the libraries that work, and the code
Published
If you are looking for a Java resume parser you can add to a pom.xml today, the honest answer is that Java has the best document readers of any ecosystem and no maintained resume-field parser. Apache Tika will hand you the text of a PDF, a DOCX, an RTF and about a thousand other file types through one interface — but nothing in it knows what a job title is. The Java projects that promised resume fields have not had a commit in years. So the decision is not which library to depend on; it is whether you write the field extraction yourself or call something that returns typed JSON.
Every version, date and figure below was read from Maven Central, the GitHub API or the vendor's own page as of September 2026. We build ResumeJSON, a per-parse resume parsing API, so read the last section as written by an interested party — everything above it is checkable with one mvn dependency:tree, and you should check it.
The Java libraries, compared
| Library or project | Latest release | What it returns | Maintained? |
|---|---|---|---|
Apache Tika (tika-core) | 3.3.1, 20 May 2026 | Text and metadata from PDF, DOCX, RTF, HTML and ~1,000 other types | Yes — a 4.0.0 alpha is also published |
| Apache PDFBox | 3.0.7, 6 March 2026 (2.0.36 on 12 March 2026) | Text, pages, fonts and forms from a PDF | Yes — two supported lines |
Apache POI (poi-ooxml) | 5.5.1, 26 November 2025 | Text and structure from DOCX, XLSX, PPTX | Yes |
| textkernel/tx-java | — | Resume fields, via Textkernel's hosted API | Yes — last pushed 4 September 2026 |
| affinda/affinda-java | — | Resume fields, via Affinda's hosted API | Last pushed 10 April 2024 |
| iadityak/resume-parser | — | Name, email, phone and a few fields, via GATE | No — last pushed 10 May 2023 |
| dnanhkhoa/cv-parser | — | Fields by rules and patterns | No — repository archived 18 April 2018 |
Two columns decide this. "What it returns" is the gap you will be filling yourself: every maintained library on that list is a document reader, so everything between "here are 4,000 characters of text" and "here is this candidate's third-most-recent employer" is your code. "Maintained?" is the risk you take on, and it is sharper here than in most dependency choices, because resume parsing is a pile of heuristics about how humans format documents, and those formats keep moving.
Notice the shape of the bottom half of the table. The two Java projects that actually extract resume fields in your own process are a 2023 GATE pipeline and a repository archived in 2018. The two that are alive are SDKs — thin clients for somebody else's hosted parser. Nobody is maintaining a field-level resume parser as a Java library, and that is the whole reason this article has two routes rather than one.
Reading the file: the part Java does best
Whatever route you choose, you need the document's text, and Java is unusually good at this. Apache Tika describes itself as a toolkit that "detects and extracts metadata and text from over a thousand different file types (such as PPT, XLS, and PDF)", with "all of these file types … parsed through a single interface". For an upload endpoint that accepts whatever a candidate sends, that single interface is the point: you do not branch on the extension, and you do not trust it either.
<dependency>
<groupId>org.apache.tika</groupId>
<artifactId>tika-parsers-standard-package</artifactId>
<version>3.3.1</version>
</dependency>import org.apache.tika.Tika;
import java.nio.file.Path;
String text = new Tika().parseToString(Path.of("cv.pdf"));That is two lines and it covers PDF, DOCX, RTF, ODT and plain text without you knowing which one arrived. Two things to set before it reaches production: Tika.setMaxStringLength defaults to 100,000 characters, which silently truncates a long CV bundle, and Tika's detect is worth calling on its own if you want to reject a file type before parsing it rather than after.
Reach past Tika only when you need something it deliberately flattens. PDFBox is the one to use when position matters — when you want the x/y coordinates of each text run because your section detection depends on layout rather than on wording. POI is the one to use when you need DOCX structure — tables, headers, numbering — rather than a flat string. Tika sits on top of both, so adding one is not a migration.
What none of them gives you is a candidate. You have text. Nothing in it is labelled.
Route one: write the heuristics yourself
This is a legitimate choice, and in Java it usually looks like regular expressions for the unambiguous fields, then a section splitter, then per-section rules.
static final Pattern EMAIL = Pattern.compile("[\\w.+-]+@[\\w-]+\\.[\\w.-]+");
static final Pattern PHONE = Pattern.compile("\\+?\\d[\\d ()-]{7,}\\d");
Optional<String> email = EMAIL.matcher(text).results().map(MatchResult::group).findFirst();Email and phone are nearly free, and they are also the fields you needed least. The work is everything after them:
- Sections. Find the lines that are headings — usually short, often uppercase, sometimes bold, in an order nobody agrees on — and cut the document at them. A two-column CV defeats this immediately, because the text layer interleaves the columns.
- Employment history. Group the lines under
EXPERIENCEinto positions. Each position is a company, a title, a date range and some bullets, in an order that varies by template and by country. - Dates.
Jan 2019 – Present,01/2019-now,2019 —,Enero 2019. Normalising these into an ISO range is its own small library. - Skills. A comma list is easy; a paragraph of prose that happens to name six technologies is not.
- Scanned CVs. A PDF with no text layer returns an empty string from Tika, and you will get those. Handling them means OCR — Tesseract via
tess4j, which is a native dependency and a deployment decision, not apom.xmlline.
None of this is beyond a Java team. The question is whether it is the thing your team should be spending its weeks on, because it is never finished: every new template, language and layout is another rule, and the rules interact.
A useful test before you commit to this route: take twenty real CVs from your own pipeline, not from a template gallery, and count how many your heuristics get completely right — every position, every date, no invented fields. That number, not a demo on a tidy one-page CV, is what you are choosing.
Route two: call a parser, from plain Java
The alternative is to send the file to something that returns typed JSON and keep your code on the part that is actually your product. In Java this needs no SDK at all — java.net.http.HttpClient has been in the JDK since 11, and a multipart upload is a few lines:
var client = HttpClient.newHttpClient();
var file = Path.of("cv.pdf");
var boundary = "----javaresume" + System.currentTimeMillis();
var head = ("--" + boundary + "\r\n"
+ "Content-Disposition: form-data; name=\"file\"; filename=\"cv.pdf\"\r\n"
+ "Content-Type: application/pdf\r\n\r\n").getBytes(UTF_8);
var tail = ("\r\n--" + boundary + "--\r\n").getBytes(UTF_8);
var body = ByteBuffer.allocate(head.length + (int) Files.size(file) + tail.length)
.put(head).put(Files.readAllBytes(file)).put(tail).array();
var request = HttpRequest.newBuilder(URI.create(
"https://resumejson-resume-cv-parser-api.p.rapidapi.com/v1/parse"))
.header("x-rapidapi-key", System.getenv("RAPIDAPI_KEY"))
.header("x-rapidapi-host", "resumejson-resume-cv-parser-api.p.rapidapi.com")
.header("content-type", "multipart/form-data; boundary=" + boundary)
.POST(HttpRequest.BodyPublishers.ofByteArray(body))
.build();
var response = client.send(request, HttpResponse.BodyHandlers.ofString());The response has two keys: resume is the document as typed fields, meta is what happened — whether the file had a text layer or had to be read as images, how many characters were parsed, and the server-side duration. Deserialise resume straight into a record with Jackson and you are done; there is no job id to poll and no webhook to receive, because the parse returns on the same request.
The same endpoint takes JSON when you already have the text, and raw bytes when you are streaming a file through — useful if you are already holding an InputStream from a Spring MultipartFile and would rather not buffer it twice.
Which route, honestly
Stay in Java and write it yourself when the CVs are narrow and predictable — one country, one language, one recruiter uploading from one ATS export — or when the documents may not leave your network at all. A regulated employer with an on-premises rule does not have a decision to make here: Tika plus your own rules is the answer, and it is a good one.
Call an API when CVs arrive from the open internet in every template and half a dozen languages, when scanned PDFs are a real share of the intake, or when nobody on the team wants to own a date-range parser for the next three years.
And if you are comparing hosted parsers rather than routes, the honest note is that the incumbents are priced for an enterprise contract. Textkernel sells by credit and publishes plans; its tx-java SDK is the most actively maintained Java client in this space, and if you are already a Textkernel customer that SDK is the path of least resistance. Affinda's Java client exists but has not been pushed since April 2024, so on that platform you may well be writing HTTP calls anyway. We publish our own rungs on the pricing page: $0 for 100 parses a month, $0.05 a parse with no monthly fee, or $29 a month for 1,000. A parse comes back in about two seconds, one CV per call, no batch endpoint.
If you want to see the shape of the JSON before writing any of the above, the free parser takes a file in the browser with no signup. And if PHP, Node or Python is the language actually in front of you, we have the same walkthrough for PHP, Node.js and Python.