Automatiser le remplissage de formulaires PDF avec Java et PDFtk
Remplissez un formulaire PDF existant depuis une map Java, puis choisissez si ses champs restent modifiables ou deviennent du contenu de page. L’exemple ci-dessous génère du XFDF avec les API XML de Java, exécute PDFtk Java et enregistre un nouveau PDF en refusant de remplacer une destination existante.
L’opération fill_form de PDFtk
accepte du FDF ou du XFDF pour les champs AcroForm existants. Elle ne crée pas de champs dans une
page numérisée. Utilisez un modèle AcroForm fiable et non chiffré pour suivre ce guide ; les
formulaires XFA et les PDF signés nécessitent un autre flux de travail.
Prérequis
- Un JDK maintenu avec
javaetjavacdansPATH. - Linux avec Bash,
curlet GNUsha256sum, installés séparément du JDK. - Votre modèle AcroForm existant, avec des noms de champs connus et des polices couvrant votre texte.
- Un répertoire de travail accessible en écriture sur un système de fichiers prenant en charge les liens physiques.
L’environnement d’exécution vérifié utilise OpenJDK 21.0.12.1 avec PDFtk Java 3.3.3 sous Linux. Ce sont les versions testées, pas une version minimale intrinsèquement requise de Java. Utilisez une version maintenue du JDK 21, telle que Temurin 21.0.12.1, en suivant son guide d’installation depuis une archive. Les commandes d’installation ci-dessous ciblent Linux ; la configuration sous macOS et Windows sort du cadre de ce guide.
Configurer PDFtk dans votre environnement Java
PDFtk Java est le portage Java de PDFtk. Son guide d’installation pour cette version propose un JAR autonome contenant ses dépendances. Téléchargez ce JAR dans un nouveau répertoire de travail pour que le programme sélectionne une version connue de PDFtk sans modifier une installation système.
Vérifiez les prérequis distincts de la ligne de commande avant de créer des fichiers. Collez ce bloc
dans Bash depuis un répertoire parent accessible en écriture ; en cas de succès, votre shell se
trouve dans pdf-form-work :
java -version && javac -version &&
command -v curl && command -v sha256sum &&
mkdir pdf-form-work && cd pdf-form-work
Si le répertoire existe déjà, ce bloc s’arrête. Conservez son contenu et choisissez un nouveau nom,
ou entrez dans le répertoire que vous aviez créé pour reprendre une configuration partielle.
Depuis pdf-form-work, téléchargez et vérifiez le JAR :
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
La somme de contrôle correspond aux
métadonnées du paquet publiées par son éditeur.
La sortie indiquant la version doit inclure pdftk port to java 3.3.3. Si le téléchargement ou
la vérification échoue, réexécutez le bloc de téléchargement dans ce même répertoire qui vous
appartient : il remplace uniquement le JAR téléchargé. Ne poursuivez pas après l’échec d’une
vérification. Toutes les commandes restantes s’exécutent depuis pdf-form-work.
Extraire les données des champs de formulaires PDF avec PDFtk
Placez votre modèle dans ce répertoire sous le nom template.pdf. Examinez les noms
des champs et leurs valeurs d’exportation en UTF-8 :
java -jar pdftk-java-3.3.3-all.jar template.pdf dump_data_fields_utf8
Un champ de texte peut porter un nom tel que Name ; les cases à cocher et
les boutons radio indiquent également des valeurs FieldStateOption. Utilisez ces noms
et valeurs exacts, plutôt que le libellé visible ou une valeur Yes
supposée. Notez les noms que vous utiliserez avant d’écrire votre map. Même si une commande PDFtk
réussit, un champ inconnu peut rester vide.
Automatiser le remplissage de formulaires avec Java
Enregistrez cet exemple complet sous le nom PdfFormFiller.java.
fillForm accepte une map de noms de champs et de valeurs. L’exemple en ligne de
commande fournit Name et Date ; adaptez ces clés
à votre modèle après l’avoir examiné. Le JAR doit rester dans le répertoire de travail lorsque vous
exécutez le programme.
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);
}
}
}
Compilez et exécutez le programme depuis le répertoire contenant le code source, le JAR et le
modèle. Le && empêche l’exécution d’une classe issue d’une compilation
précédente si la compilation échoue. Le chemin de classes explicite sélectionne cet exemple même
si le projet qui l’englobe définit CLASSPATH :
javac -encoding UTF-8 -cp . PdfFormFiller.java &&
java -cp . PdfFormFiller template.pdf filled_form.pdf
En cas de succès, le programme affiche Filled PDF saved et crée
filled_form.pdf. Pour un modèle contenant les champs Name
et Date de l’exemple, les valeurs Jane Doe et
2026-09-15 doivent apparaître à ces emplacements. Une nouvelle exécution de la
commande refuse cette destination et laisse ses octets inchangés. Choisissez un nouveau nom de
fichier de sortie pour un autre enregistrement.
Le répertoire temporaire contient les valeurs XFDF et le PDF provisoire. Conservez ce répertoire de
travail avec des droits d’accès adaptés à ces données. La publication utilise
Files.createLink
pour refuser une destination qui apparaît pendant la conversion ; elle nécessite la prise en charge
des liens physiques et ne chiffre pas le PDF. Le bloc finally supprime les
fichiers intermédiaires lors d’une exécution normale ; un arrêt forcé de la JVM peut les laisser en
place.
Choisir une sortie modifiable ou aplatie
Le dernier true dans l’appel à fillForm de l’exemple
ajoute l’option flatten de PDFtk, qui intègre l’apparence des champs à la
page. Pour garder les champs modifiables, remplacez cet argument par
false, recompilez et écrivez vers une nouvelle destination :
javac -encoding UTF-8 -cp . PdfFormFiller.java &&
java -cp . PdfFormFiller template.pdf editable_form.pdf
L’aplatissement ne fournit ni contrôle d’accès ni protection contre les altérations.
Appelez fillForm avec une nouvelle map pour chaque modèle vérifié. Par
exemple, utilisez la valeur d’exportation indiquée pour une case à cocher, telle que
Accepted, plutôt qu’une chaîne représentant un booléen. Un champ multiligne
accepte un saut de ligne dans sa valeur. Conservez des enregistrements fictifs dans le code pendant
la vérification d’un modèle, puis fournissez les données réelles depuis votre application plutôt
que de les intégrer aux fichiers source.
Vérifier les valeurs et le rendu
Ouvrez filled_form.pdf et comparez chaque champ visible à la valeur et à la position
prévues. Le programme vérifie l’état du processus et que le fichier de sortie n’est pas vide ; il
ne peut pas prouver que chaque champ a été correctement rempli. Examinez un résultat modifiable
avant de décider d’aplatir un nouveau modèle.
Pour la variante modifiable, examinez également les valeurs enregistrées :
java -jar pdftk-java-3.3.3-all.jar editable_form.pdf dump_data_fields_utf8
Comparez FieldValue à votre map. Un PDF aplati ne contient plus de champs
interactifs à examiner ; cette commande ne peut donc pas remplacer l’examen du contenu visible de
ses pages.
- Les valeurs des cases à cocher et des boutons radio doivent correspondre aux valeurs d’exportation du modèle.
- Les champs de choix peuvent avoir des libellés d’affichage différents de leurs valeurs enregistrées ; examinez les deux.
- Cet exemple fondé sur une map écrit une valeur en texte brut par champ, pas du texte enrichi ni une liste de sélections.
- UTF-8 et l’échappement XML préservent du texte tel que
Zoë & <Ada>, mais les polices du modèle doivent tout de même contenir ces glyphes. Les limites des champs peuvent tronquer un texte long même si la valeur enregistrée est correcte.
Résoudre les problèmes courants
PDFtk ne démarre pas
Exécutez la commande indiquant la version du JAR depuis le même répertoire de travail que le
programme Java. Vérifiez que le JAR vérifié s’y trouve et que java est
toujours dans PATH. Un alias shell pour pdftk
est inutile ; le programme lance directement le JAR.
Problèmes de droits d’accès
Le processus a besoin d’un accès en lecture au modèle et d’un accès en écriture au répertoire de destination. Ce système de fichiers doit également prendre en charge les liens physiques. Si la publication échoue, ne vous rabattez pas sur une copie qui écrase les fichiers ; choisissez un stockage compatible ou implémentez une alternative qui exclut explicitement tout écrasement.
Traiter les PDF chiffrés
Les modèles chiffrés nécessitent une conception distincte pour la gestion des mots de passe. PDFtk
prend en charge input_pw, mais placer des mots de passe dans les arguments du
processus peut les exposer lors de l’inspection des processus. Ne codez pas les mots de passe en
dur et ne les copiez pas dans les journaux. Utilisez un flux de travail sécurisé adapté à votre
environnement plutôt que d’ajouter des secrets à la ligne de commande de ce tutoriel. Réécrire un
PDF signé invalide également sa signature.
Noms ou valeurs de champs incorrects
Vérifiez les noms exacts des champs et leurs valeurs d’exportation avec
dump_data_fields_utf8. Comparez un résultat non aplati à la map prévue, puis examinez
visuellement le résultat aplati. Les parenthèses, esperluettes, guillemets et barres obliques
inverses sont gérés par le sérialiseur XML, pas par une fonction d’échappement FDF écrite à la main.
Les glyphes manquants nécessitent des polices adaptées dans le modèle ; changer l’encodage XML ne
résout pas ce problème.
