Remove PDFBox from pom.xml or build.gradle and add pdfluent to Cargo.toml.
<!-- pom.xml -->
<dependency>
<groupId>org.apache.pdfbox</groupId>
<artifactId>pdfbox</artifactId>
<version>3.0.1</version>
</dependency># Cargo.toml
[dependencies]
pdfluent = "1.1.0"PDFBox uses PDDocument.load() with a File or byte array. PDFluent uses Document::open which returns a Result.
import org.apache.pdfbox.pdmodel.PDDocument;
import java.io.File;
PDDocument doc = PDDocument.load(new File("contract.pdf"));use pdfluent::PdfDocument;
let doc = PdfDocument::open("contract.pdf")?;PDFBox requires a PDFTextStripper instance and produces a single string for the whole document. PDFluent extracts per-page.
import org.apache.pdfbox.text.PDFTextStripper;
PDFTextStripper stripper = new PDFTextStripper();
stripper.setStartPage(1);
stripper.setEndPage(1);
String text = stripper.getText(doc);let text = doc.page(1)?.text()?;
// All pages
for i in 0..doc.page_count() {
let text = doc.page(i)?.text()?;
println!("{}", text);
}PDFBox accesses fields through PDDocumentCatalog and PDAcroForm. PDFluent hands out a mutable form handle with doc.form_mut(); flattening is a document-level call.
import org.apache.pdfbox.pdmodel.interactive.form.PDAcroForm;
PDAcroForm acroForm = doc.getDocumentCatalog().getAcroForm();
acroForm.getField("first_name").setValue("Jane");
acroForm.getField("last_name").setValue("Smith");
acroForm.flatten();let mut form = doc.form_mut();
form.set_text("first_name", "Jane")?;
form.set_text("last_name", "Smith")?;
doc.flatten_forms()?;PDFBox requires explicit close(). PDFluent drops the document when it goes out of scope; call save() to write.
doc.save("output.pdf");
doc.close(); // must call close() to release file handlesdoc.save("output.pdf")?;
// doc drops automatically at end of scopePoppler is a system library installed via apt, brew, or built from source. It requires C++ headers and a linker step in your build. PDFluent is a Rust crate — add it to Cargo.toml and Cargo handles everything. No system package required, no pkg-config, no build.rs linking.
# Install system dependency
apt-get install -y libpoppler-cpp-dev
# CMakeLists.txt
find_package(PkgConfig REQUIRED)
pkg_check_modules(POPPLER REQUIRED poppler-cpp)
target_link_libraries(myapp ${POPPLER_LIBRARIES})# Cargo.toml — no system package needed
[dependencies]
pdfluent = "1.1.0"Poppler's C++ API returns raw pointers from load_from_file. You must check for null and remember to delete the document when done. PDFluent returns a Result. The ? operator handles errors, and the document is freed when it goes out of scope.
#include <poppler/cpp/poppler-document.h>
#include <poppler/cpp/poppler-page.h>
poppler::document* doc =
poppler::document::load_from_file("input.pdf");
if (!doc) {
std::cerr << "Failed to open" << std::endl;
return 1;
}
poppler::page* p = doc->create_page(0);
std::vector<poppler::text_box> boxes = p->text_list();
delete p;
delete doc;use pdfluent::PdfDocument;
fn process() -> pdfluent::Result<()> {
let doc = PdfDocument::open("input.pdf")?;
let text = doc.page(1)?.text()?;
println!("{}", text);
Ok(())
}Poppler is licensed under GPL-2.0. Any software that links against it as a library must also be GPL-licensed or obtain a separate commercial exception. PDFluent offers both routes from one publisher: the AGPL if copyleft is no obstacle, or a yearly commercial licence, from €999 for one organisation, that lifts it.
# Poppler GPL-2.0:
# Your application must be GPL-licensed if it links
# against Poppler directly as a library.
# Commercial use requires legal review or a separate
# arrangement with each downstream user.# PDFluent — two routes, one publisher:
# AGPLv3 if copyleft is no obstacle, or a
# yearly commercial licence that lifts it.
# Published price, bought with a card.C++ PDF code allocates and frees objects manually. Poppler hands you raw pointers; you decide when to delete them. In Rust, the compiler tracks ownership at compile time. Values are freed when they go out of scope. There are no delete calls.
// libpoppler-cpp
#include <poppler/cpp/poppler-document.h>
#include <poppler/cpp/poppler-page.h>
poppler::document* doc =
poppler::document::load_from_file("report.pdf");
if (!doc) {
std::cerr << "Failed to open PDF" << std::endl;
return 1;
}
poppler::page* p = doc->create_page(0);
// must manually delete both
delete p;
delete doc;use pdfluent::PdfDocument;
fn process() -> pdfluent::Result<()> {
let doc = PdfDocument::open("report.pdf")?;
// doc and page freed automatically when they go out of scope
Ok(())
}C++ PDF libraries pass raw pointers for page objects, text content, and annotations. You must check for null pointers at every step. PDFluent methods return Option or Result — the compiler forces you to handle the failure case, eliminating null pointer crashes.
poppler::page* page = doc->create_page(0);
if (!page) return 1;
std::vector<poppler::text_box> boxes = page->text_list();
for (const auto& box : boxes) {
std::string text = box.text().to_latin1();
std::cout << text << std::endl;
}
delete page;let page = doc.page(1)?;
let text = page.text()?;
println!("{}", text);C++ projects link against poppler or MuPDF through CMake or pkg-config. Cargo handles all dependency resolution automatically. Remove the C++ library linkage from your build scripts and add PDFluent to Cargo.toml.
# CMakeLists.txt
find_package(PkgConfig REQUIRED)
pkg_check_modules(POPPLER REQUIRED poppler-cpp)
target_include_directories(myapp PRIVATE
${POPPLER_INCLUDE_DIRS})
target_link_libraries(myapp PRIVATE
${POPPLER_LIBRARIES})# Cargo.toml
[dependencies]
pdfluent = "1.1.0"
# cargo build --release
# No pkg-config, no system library requiredJava PDF libraries use object graphs you mutate through method calls. PDFluent exposes the same operations — open, read, fill, save — through a Rust API. Start by listing every PDF operation your code performs and find the PDFluent equivalent in the docs.
// pom.xml
<dependency>
<groupId>com.itextpdf</groupId>
<artifactId>itext7-core</artifactId>
<version>7.2.5</version>
<type>pom</type>
</dependency>
// Or for PDFBox:
// <groupId>org.apache.pdfbox</groupId>
// <artifactId>pdfbox</artifactId>
// <version>3.0.0</version># Cargo.toml
[dependencies]
pdfluent = "1.1.0"Java PDF libraries throw checked exceptions — you surround every call with try/catch and manage cleanup in finally blocks. Rust uses the Result type. The ? operator propagates errors up the call stack automatically, and ownership ensures resources are freed when values go out of scope. There is no finally block because there is no need for one.
import com.itextpdf.kernel.pdf.PdfDocument;
import com.itextpdf.kernel.pdf.PdfReader;
import java.io.IOException;
PdfDocument pdf = null;
try {
pdf = new PdfDocument(new PdfReader("report.pdf"));
String text = PdfTextExtractor.getTextFromPage(
pdf.getPage(1),
new LocationTextExtractionStrategy()
);
System.out.println(text);
} catch (IOException e) {
System.err.println("Failed to open PDF: " + e.getMessage());
} finally {
if (pdf != null) pdf.close();
}use pdfluent::PdfDocument;
fn process_report() -> pdfluent::Result<()> {
let doc = PdfDocument::open("report.pdf")?;
let text = doc.page(1)?.text()?;
println!("{}", text);
Ok(())
// doc is freed automatically when it goes out of scope
}A Java PDF service needs a JVM in the image. A Rust binary compiled with PDFluent has no runtime dependency. Build with cargo build --release and copy the single binary to your target.
# Dockerfile (Java / iText)
FROM eclipse-temurin:21-jre
WORKDIR /app
COPY target/myapp.jar .
# JRE layer in the image
ENTRYPOINT ["java", "-jar", "myapp.jar"]# Dockerfile (Rust / PDFluent)
FROM debian:bookworm-slim
WORKDIR /app
COPY target/release/myapp .
# single binary, no runtime
ENTRYPOINT ["./myapp"]Remove pdf from Cargo.toml and add pdfluent. Both are pure Rust, so there are no native library changes.
# Cargo.toml
[dependencies]
pdf = "0.9"# Cargo.toml
[dependencies]
pdfluent = "1.1.0"pdf-rs uses FileOptions to open a file. PDFluent uses Document::open. The error handling model is the same — both return Results.
use pdf::file::FileOptions;
let file = FileOptions::cached()
.open("document.pdf")
.unwrap();use pdfluent::PdfDocument;
let doc = PdfDocument::open("document.pdf")?;pdf-rs requires traversing content streams manually. PDFluent provides a high-level text() method per page.
use pdf::file::FileOptions;
use pdf::object::*;
let file = FileOptions::cached().open("document.pdf").unwrap();
for page in file.pages() {
let page = page.unwrap();
// Content stream must be parsed manually
// No high-level text extraction API
if let Some(content) = page.contents.as_ref() {
for op in content.operations.iter() {
// match on operator codes to extract text
println!("{:?}", op);
}
}
}use pdfluent::PdfDocument;
let doc = PdfDocument::open("document.pdf")?;
for i in 0..doc.page_count() {
let text = doc.page(i)?.text()?;
println!("{}", text);
}pdf-rs has no write API. PDFluent supports modifying existing documents, filling forms, adding annotations, and saving. This step has no pdf-rs equivalent.
// pdf-rs: no write API available
// Cannot modify documents, fill forms, or save changesuse pdfluent::PdfDocument;
let mut doc = PdfDocument::open("application.pdf")?;
// Fill AcroForm
let mut form = doc.form_mut();
form.set_text("applicant_name", "Jane Smith")?;
form.set_text("date", "2024-04-14")?;
// Validate PDF/A before saving
let report = doc.validate_pdfa(pdfluent::PdfAProfile::A1b)?;
println!("Compliant: {}", report.is_compliant());
doc.save("application_filled.pdf")?;Remove PyMuPDF and install the PDFluent Python package. The package name on PyPI is pdfluent; the compat layer that follows part of the PyMuPDF API described below ships in release 1.1.0.
pip install pymupdfpip install pdfluent==1.1.0This shows PDFluent's stable Python API, for comparison. It is PDFluent's own naming (page_count(), not len(doc)), not a claim about what the compat layer that follows part of the PyMuPDF API exposes.
import fitz # PyMuPDF
doc = fitz.open("invoice.pdf")
print(f"{len(doc)} pages")
doc.close()import pdfluent
doc = pdfluent.open("invoice.pdf")
print(f"{doc.page_count()} pages")PDFluent's native Rust and C APIs report PDF geometry using the PDF specification's own bottom-left origin. The Python compat layer that follows part of the PyMuPDF API returns coordinates with a top-left origin instead. That is an explicit SDK design decision made on 2026-10-08 so page and rectangle coordinates read the same way for code arriving from PyMuPDF.
PyMuPDF and MuPDF are trademarks of Artifex Software, Inc. PDFluent is an independent product of Innovation Trigger B.V. and is not affiliated with or endorsed by Artifex.