PDF form filling is a common requirement in enterprise applications for generating reports, invoices, contracts, and other documents. Java offers several powerful libraries for working with PDF forms.
Popular PDF Libraries for Java
1. Apache PDFBox (Open Source)
- Pros: Free, actively maintained, pure Java
- Cons: Can be verbose for complex operations
- Best for: Basic to intermediate PDF operations
2. iText (Commercial with AGPL option)
- Pros: Feature-rich, excellent form handling
- Cons: Commercial licensing for closed-source
- Best for: Professional, high-volume applications
3. OpenPDF (Open Source)
- Pros: iText fork, LGPL licensed
- Cons: Less features than iText
- Best for: Open source projects
Dependencies
Maven Dependencies
<properties>
<pdfbox.version>3.0.1</pdfbox.version>
<itext.version>7.2.5</itext.version>
<openpdf.version>1.3.30</openpdf.version>
</properties>
<!-- Apache PDFBox -->
<dependency>
<groupId>org.apache.pdfbox</groupId>
<artifactId>pdfbox</artifactId>
<version>${pdfbox.version}</version>
</dependency>
<!-- iText 7 (Core) -->
<dependency>
<groupId>com.itextpdf</groupId>
<artifactId>kernel</artifactId>
<version>${itext.version}</version>
</dependency>
<dependency>
<groupId>com.itextpdf</groupId>
<artifactId>forms</artifactId>
<version>${itext.version}</version>
</dependency>
<!-- OpenPDF -->
<dependency>
<groupId>com.github.librepdf</groupId>
<artifactId>openpdf</artifactId>
<version>${openpdf.version}</version>
</dependency>
PDF Form Concepts
Types of PDF Forms
- AcroForms: Traditional PDF forms with fields
- XFA Forms: XML-based forms (less common)
- Static Forms: Pre-rendered forms
- Dynamic Forms: Forms that can grow/shrink
Common Form Field Types
- Text fields
- Checkboxes
- Radio buttons
- Dropdown lists
- Buttons
- Signature fields
Apache PDFBox Examples
Example 1: Basic Form Filling with PDFBox
import org.apache.pdfbox.pdmodel.PDDocument;
import org.apache.pdfbox.pdmodel.interactive.form.PDAcroForm;
import org.apache.pdfbox.pdmodel.interactive.form.PDField;
import java.io.File;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
public class PDFBoxFormFiller {
public static void fillFormBasic(String inputPath, String outputPath,
Map<String, String> formData) throws IOException {
try (PDDocument document = PDDocument.load(new File(inputPath))) {
PDAcroForm acroForm = document.getDocumentCatalog().getAcroForm();
if (acroForm == null) {
throw new IllegalArgumentException("PDF does not contain forms");
}
// Fill form fields
for (Map.Entry<String, String> entry : formData.entrySet()) {
PDField field = acroForm.getField(entry.getKey());
if (field != null) {
field.setValue(entry.getValue());
} else {
System.out.println("Field not found: " + entry.getKey());
}
}
// Save filled form
document.save(outputPath);
System.out.println("Form filled successfully: " + outputPath);
}
}
public static void main(String[] args) {
try {
// Sample form data
Map<String, String> formData = new HashMap<>();
formData.put("firstName", "John");
formData.put("lastName", "Doe");
formData.put("email", "[email protected]");
formData.put("phone", "555-0123");
formData.put("address", "123 Main St");
formData.put("city", "New York");
formData.put("zipCode", "10001");
fillFormBasic("input_form.pdf", "filled_form.pdf", formData);
} catch (IOException e) {
e.printStackTrace();
}
}
}
Example 2: Advanced PDFBox Form Handling
import org.apache.pdfbox.pdmodel.PDDocument;
import org.apache.pdfbox.pdmodel.interactive.form.PDAcroForm;
import org.apache.pdfbox.pdmodel.interactive.form.PDCheckBox;
import org.apache.pdfbox.pdmodel.interactive.form.PDComboBox;
import org.apache.pdfbox.pdmodel.interactive.form.PDListBox;
import org.apache.pdfbox.pdmodel.interactive.form.PDRadioButton;
import org.apache.pdfbox.pdmodel.interactive.form.PDTextField;
import java.io.File;
import java.io.IOException;
import java.util.List;
public class AdvancedPDFBoxFormFiller {
public static class FormData {
private String fullName;
private String email;
private String phone;
private boolean acceptTerms;
private String gender;
private String country;
private List<String> interests;
private String comments;
// Constructors, getters, and setters
public FormData() {}
public FormData(String fullName, String email, String phone,
boolean acceptTerms, String gender, String country,
List<String> interests, String comments) {
this.fullName = fullName;
this.email = email;
this.phone = phone;
this.acceptTerms = acceptTerms;
this.gender = gender;
this.country = country;
this.interests = interests;
this.comments = comments;
}
// Getters and setters...
public String getFullName() { return fullName; }
public void setFullName(String fullName) { this.fullName = fullName; }
public String getEmail() { return email; }
public void setEmail(String email) { this.email = email; }
public String getPhone() { return phone; }
public void setPhone(String phone) { this.phone = phone; }
public boolean isAcceptTerms() { return acceptTerms; }
public void setAcceptTerms(boolean acceptTerms) { this.acceptTerms = acceptTerms; }
public String getGender() { return gender; }
public void setGender(String gender) { this.gender = gender; }
public String getCountry() { return country; }
public void setCountry(String country) { this.country = country; }
public List<String> getInterests() { return interests; }
public void setInterests(List<String> interests) { this.interests = interests; }
public String getComments() { return comments; }
public void setComments(String comments) { this.comments = comments; }
}
public static void fillAdvancedForm(String inputPath, String outputPath,
FormData formData) throws IOException {
try (PDDocument document = PDDocument.load(new File(inputPath))) {
PDAcroForm acroForm = document.getDocumentCatalog().getAcroForm();
if (acroForm == null) {
throw new IllegalArgumentException("PDF does not contain forms");
}
// Fill text fields
setTextField(acroForm, "fullName", formData.getFullName());
setTextField(acroForm, "email", formData.getEmail());
setTextField(acroForm, "phone", formData.getPhone());
setTextField(acroForm, "comments", formData.getComments());
// Fill checkbox
setCheckbox(acroForm, "acceptTerms", formData.isAcceptTerms());
// Fill radio button (gender)
setRadioButton(acroForm, "gender", formData.getGender());
// Fill dropdown (country)
setComboBox(acroForm, "country", formData.getCountry());
// Fill list box (multiple selection)
setListBox(acroForm, "interests", formData.getInterests());
// Flatten form (make fields non-editable)
acroForm.flatten();
// Save filled form
document.save(outputPath);
System.out.println("Advanced form filled successfully: " + outputPath);
}
}
private static void setTextField(PDAcroForm acroForm, String fieldName, String value) {
if (value != null) {
try {
PDTextField field = (PDTextField) acroForm.getField(fieldName);
if (field != null) {
field.setValue(value);
}
} catch (Exception e) {
System.err.println("Error setting text field '" + fieldName + "': " + e.getMessage());
}
}
}
private static void setCheckbox(PDAcroForm acroForm, String fieldName, boolean checked) {
try {
PDCheckBox field = (PDCheckBox) acroForm.getField(fieldName);
if (field != null) {
if (checked) {
field.check();
} else {
field.unCheck();
}
}
} catch (Exception e) {
System.err.println("Error setting checkbox '" + fieldName + "': " + e.getMessage());
}
}
private static void setRadioButton(PDAcroForm acroForm, String fieldName, String value) {
if (value != null) {
try {
PDRadioButton field = (PDRadioButton) acroForm.getField(fieldName);
if (field != null) {
field.setValue(value);
}
} catch (Exception e) {
System.err.println("Error setting radio button '" + fieldName + "': " + e.getMessage());
}
}
}
private static void setComboBox(PDAcroForm acroForm, String fieldName, String value) {
if (value != null) {
try {
PDComboBox field = (PDComboBox) acroForm.getField(fieldName);
if (field != null) {
field.setValue(value);
}
} catch (Exception e) {
System.err.println("Error setting combo box '" + fieldName + "': " + e.getMessage());
}
}
}
private static void setListBox(PDAcroForm acroForm, String fieldName, List<String> values) {
if (values != null && !values.isEmpty()) {
try {
PDListBox field = (PDListBox) acroForm.getField(fieldName);
if (field != null) {
// For multiple selection
field.setValue(values.toArray(new String[0]));
}
} catch (Exception e) {
System.err.println("Error setting list box '" + fieldName + "': " + e.getMessage());
}
}
}
public static void main(String[] args) {
try {
FormData formData = new FormData(
"Jane Smith",
"[email protected]",
"555-9876",
true,
"female",
"United States",
List.of("Technology", "Sports"),
"This is a test comment for the form."
);
fillAdvancedForm("advanced_form.pdf", "filled_advanced_form.pdf", formData);
} catch (IOException e) {
e.printStackTrace();
}
}
}
Example 3: PDFBox Form Field Inspection
import org.apache.pdfbox.pdmodel.PDDocument;
import org.apache.pdfbox.pdmodel.interactive.form.PDAcroForm;
import org.apache.pdfbox.pdmodel.interactive.form.PDField;
import java.io.File;
import java.io.IOException;
import java.util.List;
public class PDFFormInspector {
public static void inspectForm(String pdfPath) throws IOException {
try (PDDocument document = PDDocument.load(new File(pdfPath))) {
PDAcroForm acroForm = document.getDocumentCatalog().getAcroForm();
if (acroForm == null) {
System.out.println("No forms found in PDF");
return;
}
System.out.println("=== PDF Form Inspection ===");
System.out.println("Form Fields Count: " + acroForm.getFields().size());
System.out.println("Form Needs Appearances: " + acroForm.getNeedAppearances());
System.out.println();
// List all fields
List<PDField> fields = acroForm.getFields();
for (PDField field : fields) {
printFieldInfo(field, 0);
}
}
}
private static void printFieldInfo(PDField field, int indent) {
String indentStr = " ".repeat(indent);
System.out.println(indentStr + "Field Name: " + field.getFullyQualifiedName());
System.out.println(indentStr + " Type: " + field.getClass().getSimpleName());
System.out.println(indentStr + " Value: " + field.getValueAsString());
System.out.println(indentStr + " Partial Name: " + field.getPartialName());
System.out.println(indentStr + " Alternate Name: " + field.getAlternateFieldName());
// Check if field has kids (nested fields)
List<PDField> kids = field.getKids();
if (kids != null && !kids.isEmpty()) {
System.out.println(indentStr + " Child Fields:");
for (PDField kid : kids) {
printFieldInfo(kid, indent + 2);
}
}
System.out.println();
}
public static void main(String[] args) {
try {
inspectForm("sample_form.pdf");
} catch (IOException e) {
e.printStackTrace();
}
}
}
iText Examples
Example 4: iText 7 Form Filling
import com.itextpdf.forms.PdfAcroForm;
import com.itextpdf.forms.fields.PdfFormField;
import com.itextpdf.kernel.pdf.PdfDocument;
import com.itextpdf.kernel.pdf.PdfReader;
import com.itextpdf.kernel.pdf.PdfWriter;
import java.io.File;
import java.io.IOException;
import java.util.Map;
public class ITextFormFiller {
public static void fillFormWithIText(String inputPath, String outputPath,
Map<String, String> formData) throws IOException {
try (PdfDocument pdfDoc = new PdfDocument(
new PdfReader(inputPath),
new PdfWriter(outputPath))) {
PdfAcroForm form = PdfAcroForm.getAcroForm(pdfDoc, true);
if (form == null) {
throw new IllegalArgumentException("PDF does not contain forms");
}
Map<String, PdfFormField> fields = form.getFormFields();
// Fill form fields
for (Map.Entry<String, String> entry : formData.entrySet()) {
PdfFormField field = fields.get(entry.getKey());
if (field != null) {
field.setValue(entry.getValue());
} else {
System.out.println("Field not found: " + entry.getKey());
}
}
// Flatten form (optional - makes fields non-editable)
form.flattenFields();
System.out.println("Form filled successfully with iText: " + outputPath);
}
}
public static void fillFormPartial(String inputPath, String outputPath,
Map<String, String> formData,
boolean flatten) throws IOException {
try (PdfDocument pdfDoc = new PdfDocument(
new PdfReader(inputPath),
new PdfWriter(outputPath))) {
PdfAcroForm form = PdfAcroForm.getAcroForm(pdfDoc, true);
for (Map.Entry<String, String> entry : formData.entrySet()) {
form.getField(entry.getKey()).setValue(entry.getValue());
}
if (flatten) {
form.flattenFields();
}
}
}
}
OpenPDF Examples
Example 5: OpenPDF Form Filling
import com.lowagie.text.pdf.AcroFields;
import com.lowagie.text.pdf.PdfReader;
import com.lowagie.text.pdf.PdfStamper;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.Map;
public class OpenPDFFormFiller {
public static void fillFormWithOpenPDF(String inputPath, String outputPath,
Map<String, String> formData) throws IOException {
PdfReader reader = new PdfReader(inputPath);
PdfStamper stamper = new PdfStamper(reader, new FileOutputStream(outputPath));
AcroFields form = stamper.getAcroFields();
// Fill form fields
for (Map.Entry<String, String> entry : formData.entrySet()) {
if (form.getField(entry.getKey()) != null) {
form.setField(entry.getKey(), entry.getValue());
} else {
System.out.println("Field not found: " + entry.getKey());
}
}
// Flatten form (make fields non-editable)
stamper.setFormFlattening(true);
stamper.close();
reader.close();
System.out.println("Form filled successfully with OpenPDF: " + outputPath);
}
}
Practical Application Examples
Example 6: Invoice Generation System
import org.apache.pdfbox.pdmodel.PDDocument;
import org.apache.pdfbox.pdmodel.interactive.form.PDAcroForm;
import java.io.File;
import java.io.IOException;
import java.time.LocalDate;
import java.time.format.DateTimeFormatter;
import java.util.HashMap;
import java.util.Map;
public class InvoiceGenerator {
public static class InvoiceData {
private String invoiceNumber;
private LocalDate invoiceDate;
private LocalDate dueDate;
private String customerName;
private String customerAddress;
private String customerEmail;
private Map<String, InvoiceItem> items;
private double taxRate;
private String notes;
// Constructors, getters, and setters
public InvoiceData() {
this.items = new HashMap<>();
}
public void addItem(String description, int quantity, double unitPrice) {
String key = "item" + (items.size() + 1);
items.put(key, new InvoiceItem(description, quantity, unitPrice));
}
public double getSubtotal() {
return items.values().stream()
.mapToDouble(InvoiceItem::getTotal)
.sum();
}
public double getTaxAmount() {
return getSubtotal() * taxRate / 100;
}
public double getTotal() {
return getSubtotal() + getTaxAmount();
}
// Getters and setters...
public String getInvoiceNumber() { return invoiceNumber; }
public void setInvoiceNumber(String invoiceNumber) { this.invoiceNumber = invoiceNumber; }
public LocalDate getInvoiceDate() { return invoiceDate; }
public void setInvoiceDate(LocalDate invoiceDate) { this.invoiceDate = invoiceDate; }
public LocalDate getDueDate() { return dueDate; }
public void setDueDate(LocalDate dueDate) { this.dueDate = dueDate; }
public String getCustomerName() { return customerName; }
public void setCustomerName(String customerName) { this.customerName = customerName; }
public String getCustomerAddress() { return customerAddress; }
public void setCustomerAddress(String customerAddress) { this.customerAddress = customerAddress; }
public String getCustomerEmail() { return customerEmail; }
public void setCustomerEmail(String customerEmail) { this.customerEmail = customerEmail; }
public Map<String, InvoiceItem> getItems() { return items; }
public void setItems(Map<String, InvoiceItem> items) { this.items = items; }
public double getTaxRate() { return taxRate; }
public void setTaxRate(double taxRate) { this.taxRate = taxRate; }
public String getNotes() { return notes; }
public void setNotes(String notes) { this.notes = notes; }
}
public static class InvoiceItem {
private String description;
private int quantity;
private double unitPrice;
public InvoiceItem(String description, int quantity, double unitPrice) {
this.description = description;
this.quantity = quantity;
this.unitPrice = unitPrice;
}
public double getTotal() {
return quantity * unitPrice;
}
// Getters and setters...
public String getDescription() { return description; }
public void setDescription(String description) { this.description = description; }
public int getQuantity() { return quantity; }
public void setQuantity(int quantity) { this.quantity = quantity; }
public double getUnitPrice() { return unitPrice; }
public void setUnitPrice(double unitPrice) { this.unitPrice = unitPrice; }
}
public static void generateInvoice(String templatePath, String outputPath,
InvoiceData invoiceData) throws IOException {
try (PDDocument document = PDDocument.load(new File(templatePath))) {
PDAcroForm acroForm = document.getDocumentCatalog().getAcroForm();
if (acroForm == null) {
throw new IllegalArgumentException("Invoice template does not contain forms");
}
DateTimeFormatter dateFormatter = DateTimeFormatter.ofPattern("MM/dd/yyyy");
// Fill basic invoice information
setFieldValue(acroForm, "invoiceNumber", invoiceData.getInvoiceNumber());
setFieldValue(acroForm, "invoiceDate",
invoiceData.getInvoiceDate().format(dateFormatter));
setFieldValue(acroForm, "dueDate",
invoiceData.getDueDate().format(dateFormatter));
// Fill customer information
setFieldValue(acroForm, "customerName", invoiceData.getCustomerName());
setFieldValue(acroForm, "customerAddress", invoiceData.getCustomerAddress());
setFieldValue(acroForm, "customerEmail", invoiceData.getCustomerEmail());
// Fill line items
int itemIndex = 1;
for (InvoiceItem item : invoiceData.getItems().values()) {
setFieldValue(acroForm, "itemDesc_" + itemIndex, item.getDescription());
setFieldValue(acroForm, "itemQty_" + itemIndex, String.valueOf(item.getQuantity()));
setFieldValue(acroForm, "itemPrice_" + itemIndex,
String.format("%.2f", item.getUnitPrice()));
setFieldValue(acroForm, "itemTotal_" + itemIndex,
String.format("%.2f", item.getTotal()));
itemIndex++;
}
// Fill totals
setFieldValue(acroForm, "subtotal",
String.format("%.2f", invoiceData.getSubtotal()));
setFieldValue(acroForm, "taxRate",
String.format("%.2f%%", invoiceData.getTaxRate()));
setFieldValue(acroForm, "taxAmount",
String.format("%.2f", invoiceData.getTaxAmount()));
setFieldValue(acroForm, "total",
String.format("%.2f", invoiceData.getTotal()));
// Fill notes
setFieldValue(acroForm, "notes", invoiceData.getNotes());
// Flatten form
acroForm.flatten();
// Save invoice
document.save(outputPath);
System.out.println("Invoice generated: " + outputPath);
}
}
private static void setFieldValue(PDAcroForm acroForm, String fieldName, String value) {
if (value != null) {
try {
var field = acroForm.getField(fieldName);
if (field != null) {
field.setValue(value);
}
} catch (Exception e) {
System.err.println("Error setting field '" + fieldName + "': " + e.getMessage());
}
}
}
public static void main(String[] args) {
try {
// Create sample invoice data
InvoiceData invoice = new InvoiceData();
invoice.setInvoiceNumber("INV-2024-001");
invoice.setInvoiceDate(LocalDate.now());
invoice.setDueDate(LocalDate.now().plusDays(30));
invoice.setCustomerName("ABC Corporation");
invoice.setCustomerAddress("123 Business Ave, Suite 100\nNew York, NY 10001");
invoice.setCustomerEmail("[email protected]");
invoice.setTaxRate(8.875);
invoice.setNotes("Thank you for your business!");
// Add line items
invoice.addItem("Web Development Services", 40, 125.00);
invoice.addItem("UI/UX Design", 20, 95.00);
invoice.addItem("Consulting", 10, 150.00);
// Generate invoice
generateInvoice("invoice_template.pdf", "generated_invoice.pdf", invoice);
} catch (IOException e) {
e.printStackTrace();
}
}
}
Example 7: Batch Form Processing
import org.apache.pdfbox.pdmodel.PDDocument;
import org.apache.pdfbox.pdmodel.interactive.form.PDAcroForm;
import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
public class BatchFormProcessor {
public static class BatchJob {
private String inputFile;
private String outputFile;
private Map<String, String> formData;
public BatchJob(String inputFile, String outputFile, Map<String, String> formData) {
this.inputFile = inputFile;
this.outputFile = outputFile;
this.formData = formData;
}
// Getters
public String getInputFile() { return inputFile; }
public String getOutputFile() { return outputFile; }
public Map<String, String> getFormData() { return formData; }
}
public static void processBatch(List<BatchJob> jobs, int threadPoolSize) {
ExecutorService executor = Executors.newFixedThreadPool(threadPoolSize);
List<Future<Boolean>> futures = new ArrayList<>();
for (BatchJob job : jobs) {
Future<Boolean> future = executor.submit(() -> {
try {
processSingleJob(job);
return true;
} catch (Exception e) {
System.err.println("Failed to process: " + job.getInputFile() + " - " + e.getMessage());
return false;
}
});
futures.add(future);
}
// Wait for completion
int successCount = 0;
for (Future<Boolean> future : futures) {
try {
if (future.get()) {
successCount++;
}
} catch (Exception e) {
System.err.println("Error waiting for job: " + e.getMessage());
}
}
executor.shutdown();
System.out.println("Batch processing completed: " + successCount + "/" + jobs.size() + " successful");
}
private static void processSingleJob(BatchJob job) throws IOException {
try (PDDocument document = PDDocument.load(new File(job.getInputFile()))) {
PDAcroForm acroForm = document.getDocumentCatalog().getAcroForm();
if (acroForm != null) {
for (Map.Entry<String, String> entry : job.getFormData().entrySet()) {
var field = acroForm.getField(entry.getKey());
if (field != null) {
field.setValue(entry.getValue());
}
}
acroForm.flatten();
}
// Ensure output directory exists
Path outputPath = Paths.get(job.getOutputFile());
Files.createDirectories(outputPath.getParent());
document.save(job.getOutputFile());
}
}
public static void main(String[] args) {
// Example batch processing
List<BatchJob> jobs = new ArrayList<>();
for (int i = 1; i <= 10; i++) {
Map<String, String> formData = new HashMap<>();
formData.put("name", "Customer " + i);
formData.put("email", "customer" + i + "@example.com");
formData.put("accountNumber", "ACC" + String.format("%04d", i));
jobs.add(new BatchJob(
"templates/form_template.pdf",
"output/customer_" + i + "_filled.pdf",
formData
));
}
processBatch(jobs, 4); // Process with 4 threads
}
}
Best Practices and Error Handling
Example 8: Robust Form Filling with Error Handling
import org.apache.pdfbox.pdmodel.PDDocument;
import org.apache.pdfbox.pdmodel.interactive.form.PDAcroForm;
import java.io.File;
import java.io.IOException;
import java.util.Map;
public class RobustFormFiller {
public static class FormFillResult {
private boolean success;
private String message;
private String outputPath;
private List<String> missingFields;
public FormFillResult(boolean success, String message, String outputPath) {
this.success = success;
this.message = message;
this.outputPath = outputPath;
this.missingFields = new ArrayList<>();
}
// Getters and setters
public boolean isSuccess() { return success; }
public String getMessage() { return message; }
public String getOutputPath() { return outputPath; }
public List<String> getMissingFields() { return missingFields; }
public void addMissingField(String field) { missingFields.add(field); }
}
public static FormFillResult fillFormSafely(String inputPath, String outputPath,
Map<String, String> formData) {
try {
// Validate inputs
if (!new File(inputPath).exists()) {
return new FormFillResult(false, "Input file does not exist: " + inputPath, null);
}
File outputFile = new File(outputPath);
if (outputFile.exists() && !outputFile.canWrite()) {
return new FormFillResult(false, "Cannot write to output file: " + outputPath, null);
}
// Process PDF
try (PDDocument document = PDDocument.load(new File(inputPath))) {
PDAcroForm acroForm = document.getDocumentCatalog().getAcroForm();
if (acroForm == null) {
return new FormFillResult(false, "PDF does not contain forms", null);
}
FormFillResult result = new FormFillResult(true, "Form filled successfully", outputPath);
// Fill fields and track missing ones
for (Map.Entry<String, String> entry : formData.entrySet()) {
var field = acroForm.getField(entry.getKey());
if (field != null) {
try {
field.setValue(entry.getValue());
} catch (Exception e) {
System.err.println("Error setting field '" + entry.getKey() + "': " + e.getMessage());
result.addMissingField(entry.getKey());
}
} else {
result.addMissingField(entry.getKey());
}
}
// Flatten and save
acroForm.flatten();
document.save(outputPath);
if (!result.getMissingFields().isEmpty()) {
result.setSuccess(true); // Still successful but with warnings
result.setMessage("Form filled with missing fields: " + result.getMissingFields());
}
return result;
} catch (IOException e) {
return new FormFillResult(false, "PDF processing error: " + e.getMessage(), null);
}
} catch (Exception e) {
return new FormFillResult(false, "Unexpected error: " + e.getMessage(), null);
}
}
}
Performance Optimization Tips
- Use PDDocument pooling for high-volume processing
- Batch process multiple forms
- Use threading for concurrent processing
- Cache template PDFs in memory
- Pre-compile form field mappings
- Use memory-mapped files for large PDFs
Security Considerations
- Validate PDF sources to prevent malicious content
- Sanitize form data to prevent injection attacks
- Use temporary files securely
- Implement proper access controls for generated PDFs
Conclusion
Library Comparison
| Library | License | Ease of Use | Features | Performance |
|---|---|---|---|---|
| PDFBox | Apache 2.0 | Good | Very Good | Good |
| iText | AGPL/Commercial | Excellent | Excellent | Excellent |
| OpenPDF | LGPL | Good | Good | Good |
Choosing the Right Library
- PDFBox: Best for open-source projects and basic to intermediate needs
- iText: Best for commercial applications requiring advanced features
- OpenPDF: Good balance for open-source projects needing iText-like features
Common Use Cases
- Invoice Generation: Automated invoice creation
- Contract Management: Pre-filled legal documents
- Application Forms: Government and business forms
- Reports: Data-driven PDF reports
- Certificates: Automated certificate generation
PDF form filling in Java is a powerful capability that can automate many business processes. Choose the library that best fits your project's requirements, licensing needs, and complexity level.
Next Steps: Explore digital signature capabilities, PDF/A compliance for archiving, or integrate with template engines for dynamic PDF generation.