StackPractices
beginner By Mathias Paulenko

Parse PDF Files in Python, JavaScript and Java

Extract text, tables and metadata from PDF files with Python, JavaScript and Java, including encrypted files and scanned documents via OCR.

Topics: data

Overview

Invoices, contracts and reports still arrive as PDFs, and there’s no API to query them, so you end up pulling the text out yourself. PDF is a page-description format, not a data format: it stores glyphs at absolute coordinates, and extraction is never as clean as parsing JSON or CSV. This recipe shows the libraries that actually work for text, tables and metadata in Python, JavaScript and Java, plus the two cases that break naive parsing: encrypted files and scanned documents.

When to Use

Use this approach when:

  • Pulling invoices, receipts or forms out of PDF attachments.
  • Indexing a corpus of PDFs for full-text search.
  • Pulling tables out of financial reports or research papers.
  • Reading document properties (author, creation date) for auditing or deduplication.

Don’t use it when the PDFs are pure scans with no text layer, since those need OCR first (see Scanned and Encrypted PDFs). And if the job is creating PDFs rather than reading them, the recipe you want is Generate PDF Reports in Python.

Solution

Every snippet below runs as-is. A runnable version lives in the companion repository, including a script that generates a sample PDF to practice on.

Python

pypdf (the maintained successor of PyPDF2) covers text extraction and metadata:

# pip install pypdf
from pypdf import PdfReader

reader = PdfReader("document.pdf")
print(f"Pages: {len(reader.pages)}")
for page in reader.pages:
    print(page.extract_text())

Metadata lives in the document info dictionary, not in the page content:

reader = PdfReader("document.pdf")
meta = reader.metadata
print(meta.title, meta.author, meta.creation_date)

For tables, pdfplumber detects ruling lines and text alignment:

# pip install pdfplumber
import pdfplumber

with pdfplumber.open("document.pdf") as pdf:
    for page in pdf.pages:
        for table in page.extract_tables():
            for row in table:
                print(row)

JavaScript

pdf-parse wraps Mozilla’s PDF.js and returns text plus page count:

// npm install pdf-parse
import pdfParse from "pdf-parse/lib/pdf-parse.js";
import fs from "node:fs";

const dataBuffer = fs.readFileSync("document.pdf");
const data = await pdfParse(dataBuffer);
console.log(data.text);
console.log(`Pages: ${data.numpages}`);

pdf-lib reads and edits metadata and form fields:

// npm install pdf-lib
import { PDFDocument } from "pdf-lib";
import fs from "node:fs";

const pdfDoc = await PDFDocument.load(fs.readFileSync("document.pdf"));
console.log(`Pages: ${pdfDoc.getPageCount()}`);
console.log(pdfDoc.getTitle(), pdfDoc.getAuthor());

Java

Apache PDFBox is the standard for JVM projects:

// Maven: org.apache.pdfbox:pdfbox
import org.apache.pdfbox.pdmodel.PDDocument;
import org.apache.pdfbox.text.PDFTextStripper;

try (PDDocument doc = PDDocument.load(new java.io.File("document.pdf"))) {
    PDFTextStripper stripper = new PDFTextStripper();
    System.out.println(stripper.getText(doc));
    System.out.println("Pages: " + doc.getNumberOfPages());
}

Explanation

A PDF page is a list of drawing instructions: “put glyph X at coordinate (72, 540)”. There’s no guarantee that the content stream follows visual reading order, and a two-column paper often extracts interleaved, line by line, across both columns. A file that renders perfectly can still produce scrambled text.

The libraries differ mainly in how much layout analysis they do on top of the raw text objects. pypdf concatenates text runs in content order, which is fine for single-column documents. Because pdfplumber sits on pdfminer.six it keeps the coordinates of every character, which is enough to rebuild words, lines and tables. pdf-parse delegates to PDF.js, the same engine Firefox uses to render PDFs in the browser. PDFBox gives you PDFTextStripper for plain output and PDFTextStripperByArea when you already know which region of the page holds the data.

Once you’ve pulled plain text or rows out of the PDF, downstream work is just data parsing, for example feeding extracted CSV-like rows through the same pipeline as Parse CSV Files or normalizing fields with Serialize and Deserialize Data.

Variants

TechnologyLibraryBest forNotes
PythonpypdfText, metadata, merging/splittingSuccessor of deprecated PyPDF2; pure Python
PythonpdfplumberTables and layout-aware extractionBuilt on pdfminer.six; slower but precise
PythonpymupdfSpeed, images, annotationsC-based MuPDF engine; also renders pages
PythonpikepdfEncrypted or damaged filesWraps qpdf; repairs and decrypts PDFs
JavaScriptpdf-parseQuick text extraction in NodeThin wrapper around PDF.js
JavaScriptpdf-libMetadata, forms, modificationWorks in Node and in the browser
JavaApache PDFBoxEnterprise JVM pipelinesLow-level access; powers Tabula

Pick pdfplumber when tables are the point of the exercise, pymupdf when throughput matters, and pypdf for everything else. If the pipeline has to decrypt or repair files first, put pikepdf in front.

Choosing the Right Library

flowchart diagram: PDF input

Scanned and Encrypted PDFs

Scanned PDFs store each page as an image, so extract_text() returns empty strings even though humans see text. Detect this by checking whether extraction yields only whitespace, then run OCR. The easiest route is ocrmypdf, which adds a searchable text layer to the file itself:

# Adds a hidden text layer; output parses like a normal PDF
ocrmypdf input-scan.pdf output-searchable.pdf

In Python, pdf2image plus pytesseract gives you per-page control; on cloud, AWS Textract, Google Document AI and Azure Document Intelligence handle forms and handwriting better than raw Tesseract.

Encrypted PDFs either throw or hand back empty strings until you supply the password. With pypdf you check reader.is_encrypted and then call reader.decrypt(password); owner-password restrictions and damaged files are handled more gracefully by pikepdf:

# pip install pikepdf
import pikepdf

with pikepdf.open("locked.pdf", password="secret") as pdf:
    pdf.save("unlocked.pdf")  # then parse with pypdf or pdfplumber

Best Practices

  • Sample-check extraction quality on a few pages before processing thousands of files; accuracy varies wildly depending on which tool generated the PDF.
  • Whatever the language, hold readers inside with blocks or try-with-resources so file handles and buffers actually get released. PDF parsers are greedy with both.
  • For batch jobs, parse with concurrent.futures.ProcessPoolExecutor, because PDF extraction is CPU-bound and threads don’t help.
  • pdfminer.six (and therefore pdfplumber) can hang on malformed files; in production, run it in a subprocess with a timeout.
  • Keep the extracted text alongside a hash of the source file so you can re-parse only what changed.

Common Mistakes

  • Parsing a scanned PDF and getting nothing: empty extract_text() output almost always means image-only pages, so route them to OCR.
  • Assuming visual order equals extraction order: multi-column layouts mix lines from both columns. If order matters, use pdfplumber with layout=True or extract regions by bounding box.
  • The pdf-parse debug trap: importing pdf-parse directly (instead of pdf-parse/lib/pdf-parse.js) can trigger its test-mode block and throw ENOENT on test/data/05-versions-space.pdf. This is a known packaging quirk of pdf-parse 1.x.
  • Ignoring encryption: calling extract_text() on an encrypted file raises errors or returns nothing; check is_encrypted first.
  • Treating OCR output as exact: OCR confuses 0/O, 1/l, merged cells and currency symbols, so amounts and IDs need a format check before anything downstream consumes them.

Troubleshooting

  • Empty text but the PDF looks fine: the file is probably image-only. Run pdffonts (it ships with poppler); if it lists zero fonts, there’s no text layer to read.
  • Garbled characters or missing accents: the PDF uses a custom font encoding without a ToUnicode map; try pymupdf or OCR instead.
  • Columns merged into one line: move the extraction to pdfplumber and tune its table_settings, or clip the region of interest with PDFTextStripperByArea in PDFBox.
  • Parser hangs on one file: wrap the call in a subprocess with a timeout and move the file to a quarantine folder for manual review.

See Also

  • pypdf documentation: text, metadata, encryption and merging.
  • pdfplumber on GitHub: character-level extraction and table detection.
  • Apache PDFBox: the standard JVM toolkit for PDF work.
  • PDF.js: Mozilla’s browser PDF engine, basis of pdf-parse.
  • ocrmypdf: gives scanned PDFs a searchable text layer without changing how they look.

Frequently Asked Questions

How do I install a PDF parser in Python?

Run pip install pypdf for text and metadata, or pip install pdfplumber when you need tables. Avoid PyPDF2, which was renamed to pypdf in 2022 and no longer receives updates.

What is the difference between pypdf and pdfplumber?

pypdf extracts raw text fast and handles metadata, merging and encryption basics. pdfplumber analyzes character positions, so it can find table borders and reconstruct rows; use it for invoices and reports, and accept that it's slower.

Can I parse PDFs in the browser?

Yes. Mozilla's PDF.js runs entirely client-side and extracts text per page; pdf-lib works in the browser for reading metadata and editing forms. Offload large files to a Web Worker so the UI thread stays responsive.

Why does my extracted text come out empty?

The PDF is almost certainly a scan: pages stored as images with no text layer. Check with pdffonts (no fonts = no text) and run OCR with ocrmypdf or a cloud service like Textract before extracting.

How do I extract tables from a PDF?

Use pdfplumber in Python (page.extract_tables()) or camelot for lattice-style tables. In Java, Tabula's extraction algorithms run on top of PDFBox. For borderless tables, tune table_settings with explicit vertical strategies like "text".

Can I fill or read PDF form fields?

Yes. pypdf exposes AcroForm fields via reader.get_fields(), and pdf-lib supports reading and filling form fields in Node and the browser. XFA forms (common in government PDFs) need flattening first, which pdftk or PDFBox can do.