PDF-Formulare mit Java und PDFtk automatisch ausfüllen
Füllen Sie ein vorhandenes PDF-Formular aus einer Java-Map aus und entscheiden Sie dann, ob seine Felder editierbar bleiben oder zu Seiteninhalt werden. Das folgende Beispiel erzeugt XFDF mit den XML-APIs von Java, führt PDFtk Java aus und speichert eine neue PDF-Datei, ohne ein vorhandenes Dateiziel zu ersetzen.
Der PDFtk-Vorgang fill_form
akzeptiert FDF oder XFDF für vorhandene AcroForm-Felder. Er erstellt keine Felder auf einer
gescannten Seite. Verwenden Sie für diese Anleitung eine vertrauenswürdige, unverschlüsselte
AcroForm-Vorlage; XFA-Formulare und signierte PDFs erfordern einen anderen Ablauf.
Voraussetzungen
- Ein gepflegtes JDK mit
javaundjavacinPATH. - Linux mit Bash,
curlund GNUsha256sum, unabhängig vom JDK installiert. - Ihre vorhandene AcroForm-Vorlage mit bekannten Feldnamen und Schriftarten, die Ihren Text abdecken.
- Ein beschreibbares Arbeitsverzeichnis auf einem Dateisystem, das Hardlinks unterstützt.
Die verifizierte Laufzeitumgebung ist OpenJDK 21.0.12.1 mit PDFtk Java 3.3.3 unter Linux. Dies sind getestete Versionen, keine grundsätzliche Java-Mindestanforderung. Verwenden Sie eine gepflegte JDK-21-Version, etwa Temurin 21.0.12.1, und folgen Sie der Installationsanleitung für das Archiv. Die folgenden Installationsbefehle sind für Linux vorgesehen; die Einrichtung unter macOS und Windows ist nicht Teil dieser Anleitung.
PDFtk in Ihrer Java-Umgebung einrichten
PDFtk Java ist die Java-Portierung von PDFtk. Die versionsgebundene Installationsanleitung bietet eine eigenständige JAR-Datei mit allen Abhängigkeiten. Laden Sie diese JAR-Datei in ein neues Arbeitsverzeichnis herunter, damit das Programm eine bekannte PDFtk-Version auswählt, ohne eine Systeminstallation zu ändern.
Prüfen Sie die separat benötigten Kommandozeilenwerkzeuge, bevor Sie Dateien erstellen. Fügen Sie
diesen Block in Bash ein, während Sie sich in einem beschreibbaren übergeordneten Verzeichnis
befinden; bei Erfolg befindet sich Ihre Shell anschließend in pdf-form-work:
java -version && javac -version &&
command -v curl && command -v sha256sum &&
mkdir pdf-form-work && cd pdf-form-work
Ein vorhandenes Verzeichnis stoppt diesen Block. Bewahren Sie dessen Inhalt auf und wählen Sie
einen neuen Namen, oder wechseln Sie in das zuvor erstellte Verzeichnis, um eine unvollständige
Einrichtung fortzusetzen. Laden Sie dort, in pdf-form-work, die JAR-Datei herunter
und verifizieren Sie sie:
curl -fsSLo pdftk-java-3.3.3-all.jar \
https://gitlab.com/api/v4/projects/5024297/packages/generic/pdftk-java/v3.3.3/pdftk-all.jar &&
printf '%s %s\n' \
a694d49bd03e1edd4c23b3ba808bc221eb8a8ccfe7bfd2a0a884b2b2fb425188 \
pdftk-java-3.3.3-all.jar | sha256sum -c - &&
java -jar pdftk-java-3.3.3-all.jar --version
Die Prüfsumme stimmt mit den Paketmetadaten des Herausgebers überein.
Die Versionsausgabe sollte pdftk port to java 3.3.3 enthalten. Falls der Download oder die
Verifizierung fehlschlägt, führen Sie den Download-Block erneut in demselben Verzeichnis aus, das
Ihnen gehört: Er ersetzt nur den JAR-Download. Fahren Sie nach einer fehlgeschlagenen Prüfung nicht
fort. Alle weiteren Befehle werden in pdf-form-work ausgeführt.
Formularfeldinformationen mit PDFtk aus PDFs extrahieren
Legen Sie Ihre Vorlage als template.pdf in diesem Verzeichnis ab. Prüfen Sie die
Feldnamen und Exportwerte in UTF-8:
java -jar pdftk-java-3.3.3-all.jar template.pdf dump_data_fields_utf8
Ein Textfeld kann einen Namen wie Name haben; Kontrollkästchen und
Optionsfelder geben zusätzlich Werte für FieldStateOption aus. Verwenden Sie genau
diese Namen und Werte, weder die sichtbare Beschriftung noch einen angenommenen Wert
Yes. Notieren Sie die benötigten Namen, bevor Sie Ihre Map schreiben.
Auch nach einem erfolgreichen PDFtk-Befehl kann ein unbekanntes Feld unausgefüllt bleiben.
Formularbefüllung mit Java automatisieren
Speichern Sie dieses vollständige Beispiel als PdfFormFiller.java. Die Methode
fillForm akzeptiert eine Map mit Feldnamen und Werten. Das CLI-Beispiel
übergibt Name und Date; passen Sie diese
Schlüssel an die zuvor geprüfte Vorlage an. Die JAR-Datei muss beim Ausführen des Programms im
Arbeitsverzeichnis bleiben.
import java.io.IOException;
import java.io.OutputStream;
import java.nio.file.Files;
import java.nio.file.LinkOption;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.TimeUnit;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.transform.OutputKeys;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
public class PdfFormFiller {
public static void fillForm(Path template, Path output, Map<String, String> values,
boolean flatten) throws Exception {
template = template.toAbsolutePath().normalize();
output = output.toAbsolutePath().normalize();
Path toolkit = Path.of("pdftk-java-3.3.3-all.jar").toAbsolutePath().normalize();
if (!Files.isRegularFile(toolkit)) {
throw new IOException("PDFtk Java JAR must be in the working directory");
}
if (!Files.isRegularFile(template)) {
throw new IOException("Template must be a regular file");
}
if (Files.exists(output, LinkOption.NOFOLLOW_LINKS)) {
throw new IOException("Output must not exist");
}
if (values.isEmpty() || values.entrySet().stream().anyMatch(
entry -> entry.getKey() == null || entry.getValue() == null)) {
throw new IllegalArgumentException("Supply nonnull field names and values");
}
Path temporary = Files.createTempDirectory(output.getParent(), ".pdf-form-");
Path data = temporary.resolve("data.xfdf");
Path candidate = temporary.resolve("result.pdf");
try {
writeXfdf(data, values);
List<String> arguments = new ArrayList<>(List.of("java", "-jar", toolkit.toString(),
template.toString(),
"fill_form", data.toString(), "output", candidate.toString()));
if (flatten) arguments.add("flatten");
arguments.add("dont_ask");
Process process = new ProcessBuilder(arguments)
.redirectOutput(ProcessBuilder.Redirect.DISCARD)
.redirectError(ProcessBuilder.Redirect.DISCARD).start();
process.getOutputStream().close();
try {
if (!process.waitFor(120, TimeUnit.SECONDS)) {
throw new IOException("PDFtk conversion timed out");
}
if (process.exitValue() != 0) {
throw new IOException("PDFtk conversion failed");
}
} finally {
if (process.isAlive()) {
process.destroyForcibly();
process.waitFor();
}
}
if (!Files.isRegularFile(candidate) || Files.size(candidate) == 0) {
throw new IOException("PDFtk did not produce a nonempty result");
}
// Same-filesystem publication fails if a destination appeared during conversion.
Files.createLink(output, candidate);
} finally {
Files.deleteIfExists(candidate);
Files.deleteIfExists(data);
Files.deleteIfExists(temporary);
}
}
private static void writeXfdf(Path path, Map<String, String> values) throws Exception {
String namespace = "http://ns.adobe.com/xfdf/";
Document document = DocumentBuilderFactory.newInstance().newDocumentBuilder().newDocument();
Element root = document.createElementNS(namespace, "xfdf");
document.appendChild(root);
Element fields = document.createElementNS(namespace, "fields");
root.appendChild(fields);
for (Map.Entry<String, String> entry : values.entrySet()) {
Element field = document.createElementNS(namespace, "field");
field.setAttribute("name", entry.getKey());
Element value = document.createElementNS(namespace, "value");
value.setTextContent(entry.getValue());
field.appendChild(value);
fields.appendChild(field);
}
var transformer = TransformerFactory.newInstance().newTransformer();
transformer.setOutputProperty(OutputKeys.ENCODING, "UTF-8");
try (OutputStream stream = Files.newOutputStream(path)) {
transformer.transform(new DOMSource(document), new StreamResult(stream));
}
}
public static void main(String[] args) {
if (args.length != 2) {
System.err.println("Usage: java -cp . PdfFormFiller <template.pdf> <new-output.pdf>");
System.exit(1);
}
try {
fillForm(Path.of(args[0]), Path.of(args[1]),
Map.of("Name", "Jane Doe", "Date", "2026-09-15"), true);
System.out.println("Filled PDF saved");
} catch (InterruptedException error) {
Thread.currentThread().interrupt();
System.err.println("PDF form filling interrupted");
System.exit(1);
} catch (Exception error) {
System.err.println("PDF form filling failed; check template, fields, destination, and PDFtk");
System.exit(1);
}
}
}
Kompilieren Sie das Programm und führen Sie es aus dem Verzeichnis aus, das Quellcode, JAR-Datei
und Vorlage enthält. Der Operator && verhindert, dass nach einer
fehlgeschlagenen Kompilierung eine Klasse aus einem früheren Build ausgeführt wird. Der explizite
Klassenpfad wählt dieses Beispiel aus, auch wenn Ihr übergeordnetes Projekt
CLASSPATH setzt:
javac -encoding UTF-8 -cp . PdfFormFiller.java &&
java -cp . PdfFormFiller template.pdf filled_form.pdf
Bei Erfolg gibt das Programm Filled PDF saved aus und erstellt
filled_form.pdf. Bei einer Vorlage mit den Beispielfeldern
Name und Date sollten an diesen Positionen
Jane Doe und 2026-09-15 erscheinen. Eine erneute Ausführung
des Befehls verweigert dieses Dateiziel und lässt dessen Bytes unverändert. Wählen Sie für einen
weiteren Datensatz einen neuen Ausgabedateinamen.
Das temporäre Verzeichnis enthält die XFDF-Werte und die vorläufige PDF-Datei. Versehen Sie dieses
Arbeitsverzeichnis mit Berechtigungen, die für diese Daten angemessen sind. Die Bereitstellung nutzt
Files.createLink,
um ein Dateiziel abzulehnen, das während der Konvertierung entsteht; sie erfordert
Hardlink-Unterstützung und verschlüsselt die PDF-Datei nicht. Der Block
finally entfernt die temporären Dateien bei normaler Ausführung; wird die
JVM gewaltsam beendet, können diese Dateien zurückbleiben.
Editierbare oder reduzierte Ausgabe wählen
Das letzte Argument true im Aufruf von
fillForm im Beispiel fügt die PDFtk-Option
flatten hinzu und macht die Felddarstellungen zu einem Teil der Seite.
Damit die Felder editierbar bleiben, ändern Sie dieses Argument in false,
kompilieren Sie erneut und schreiben Sie in ein neues Dateiziel:
javac -encoding UTF-8 -cp . PdfFormFiller.java &&
java -cp . PdfFormFiller template.pdf editable_form.pdf
Das Reduzieren bietet weder Zugriffskontrolle noch Manipulationsschutz.
Rufen Sie fillForm für jede überprüfte Vorlage mit einer neuen Map auf.
Verwenden Sie etwa den angegebenen Exportwert eines Kontrollkästchens, beispielsweise
Accepted, statt einer Zeichenfolge mit einem booleschen Wert. Ein
mehrzeiliges Feld akzeptiert einen Zeilenumbruch in seinem Wert. Verwenden Sie beim Prüfen einer
Vorlage synthetische Datensätze im Code. Übergeben Sie anschließend echte Daten aus Ihrer
Anwendung, statt sie in Quelldateien einzubetten.
Werte und Darstellung überprüfen
Öffnen Sie filled_form.pdf und vergleichen Sie jedes sichtbare Feld mit dem
vorgesehenen Wert und seiner Position. Das Programm prüft den Prozessstatus und ob die Ausgabedatei
nicht leer ist; es kann nicht nachweisen, dass jedes Feld korrekt ausgefüllt wurde. Prüfen Sie bei
einer neuen Vorlage zunächst ein editierbares Ergebnis, bevor Sie sich für das Reduzieren entscheiden.
Prüfen Sie bei der editierbaren Variante auch die gespeicherten Werte:
java -jar pdftk-java-3.3.3-all.jar editable_form.pdf dump_data_fields_utf8
Vergleichen Sie FieldValue mit Ihrer Map. Eine reduzierte PDF-Datei enthält
keine interaktiven Felder mehr, die Sie prüfen könnten. Dieser Befehl ersetzt daher nicht die
Prüfung des sichtbaren Seiteninhalts.
- Die Werte von Kontrollkästchen und Optionsfeldern müssen den Exportwerten der Vorlage entsprechen.
- Auswahlfelder können andere Beschriftungen anzeigen als die gespeicherten Werte; prüfen Sie beides.
- Dieses Map-basierte Beispiel schreibt einen Klartextwert pro Feld, weder Rich Text noch eine Liste ausgewählter Optionen.
- UTF-8 und XML-Escaping erhalten Text wie
Zoë & <Ada>, aber die Schriftarten der Vorlage müssen diese Glyphen weiterhin enthalten. Feldgrenzen können langen Text abschneiden, selbst wenn der gespeicherte Wert korrekt ist.
Häufige Probleme beheben
PDFtk startet nicht
Führen Sie den Befehl zur JAR-Versionsabfrage aus demselben Arbeitsverzeichnis wie das Java-Programm
aus. Prüfen Sie, ob die verifizierte JAR-Datei dort liegt und ob java
weiterhin in PATH verfügbar ist. Ein Shell-Alias für
pdftk ist nicht nötig; das Programm startet die JAR-Datei direkt.
Berechtigungsprobleme
Der Prozess benötigt Lesezugriff auf die Vorlage und Schreibzugriff auf das Zielverzeichnis. Außerdem muss das Dateisystem Hardlinks unterstützen. Falls die Bereitstellung fehlschlägt, weichen Sie nicht auf einen Kopiervorgang aus, der vorhandene Dateien überschreibt. Wählen Sie einen unterstützten Speicherort oder implementieren Sie eine Alternative, die ausdrücklich nichts überschreibt.
Umgang mit verschlüsselten PDFs
Verschlüsselte Vorlagen erfordern ein separates Konzept für den Umgang mit Passwörtern. PDFtk
unterstützt input_pw, doch Passwörter in Prozessargumenten können bei der
Prozessinspektion offengelegt werden. Hinterlegen Sie Passwörter nicht fest im Code und kopieren
Sie sie nicht in Protokolle. Verwenden Sie einen geeigneten sicheren Ablauf für Ihre Umgebung,
statt sensible Zugangsdaten in die Kommandozeile dieser Anleitung einzufügen. Das Neuschreiben
einer signierten PDF-Datei macht außerdem deren Signatur ungültig.
Falsche Feldnamen oder Werte
Prüfen Sie die genauen Feldnamen und Exportwerte mit dump_data_fields_utf8. Vergleichen
Sie ein nicht reduziertes Ergebnis mit der vorgesehenen Map und prüfen Sie anschließend das
reduzierte Ergebnis visuell. Klammern, kaufmännische Und-Zeichen, Anführungszeichen und
Backslashes werden vom XML-Serializer verarbeitet, nicht von einer handgeschriebenen Funktion
für FDF-Escaping. Fehlende Glyphen erfordern geeignete Schriftarten in der Vorlage; eine Änderung
der XML-Zeichenkodierung behebt sie nicht.
