Flatten a PDF in Java: Lock Form Fields From Spring Boot
Blog / PDF Processing
PDF Processing

Flatten a PDF in Java: Lock Form Fields From Spring Boot

Demetra Theodorou · Oct 21, 2026

Lock the fields of a filled PDF from Java and serve the result from Spring Boot. Whole forms, chosen fields, forms generated from HTML, and a plain java.net.http version. Tested code throughout.

Flattening converts a fillable PDF into a static one. The values people entered remain on the page, the fields that held them disappear, and any software reading the file sees ordinary content. It is the step between a completed form and its final destination, whether that is an archive, a printer, an inbox or a signing workflow.

This article shows how to flatten a PDF in Java with the Flatten PDF API and the official Java SDK. It covers a whole document, a chosen set of fields, a form your own code fills from HTML, the same call with java.net.http.HttpClient and no SDK, and a Spring Boot controller. Every sample on this page was compiled and run against the API before publishing.

In Java the usual route is PDFBox or iText. Both work, but PDFBox needs you to regenerate appearance streams by hand before flattening or the values vanish, and iText's AGPL licence rules it out for many commercial products. With the API, the PDF engine lives on the PDFGate side, and your code is three method calls.

Key takeaways

  • Flattening paints the field values into the page and deletes the AcroForm, so readers see content, not inputs.
  • In Java it is three calls on one client: uploadFile, flattenPdf, getFile. No PDFBox appearance regeneration, no iText licence.
  • Give fieldNames a list to lock only those fields while the rest of the form stays live.
  • Without the SDK, one multipart request from the JDK's HttpClient uploads and flattens in a single step.

What flattening does

  • Text fields, checkboxes, radio buttons, combo boxes and text areas are each rendered into the page content with the value they hold.
  • The interactive layer is removed entirely. In PDFBox, getDocumentCatalog().getAcroForm() on the output returns null.
  • The flattened file is a new document with its own ID. The source is untouched, so both versions remain available.
  • A fieldNames list restricts flattening to those fields and keeps the others interactive, for example to lock a signature while the remaining fields stay open.

Protecting a PDF with a password does not remove its form. If you need a locked and encrypted file, flatten it and then pass the result to the Protect PDF API.

Add the Java SDK

The SDK is on Maven Central as com.pdfgate:pdfgate, supports every LTS JDK from 8 through 25, and brings OkHttp and Gson with it.

Gradle:

1implementation "com.pdfgate:pdfgate:1.3.0"

Maven:

1<dependency>2  <groupId>com.pdfgate</groupId>3  <artifactId>pdfgate</artifactId>4  <version>1.3.0</version>5</dependency>

Create a key in the dashboard. The SDK sends test_ keys to the sandbox and live_ keys to production, so no base URL is configured. The listings read the key from PDFGATE_API_KEY.

Flatten a PDF file

An existing file goes through upload, flatten and download. uploadFile returns a document, flattenPdf on its ID returns another, and getFile on that one returns the bytes to write wherever they belong.

1import com.pdfgate.FileParam;2import com.pdfgate.FlattenPdfParams;3import com.pdfgate.GetFileParams;4import com.pdfgate.PdfGate;5import com.pdfgate.PdfGateDocument;6import com.pdfgate.UploadFileParams;7import java.nio.file.Files;8import java.nio.file.Path;910public class FlattenPdf {11  public static void main(String[] args) throws Exception {12    PdfGate client = new PdfGate(System.getenv("PDFGATE_API_KEY"));1314    // 1. Upload the PDF you want to flatten15    byte[] input = Files.readAllBytes(Path.of("form.pdf"));16    PdfGateDocument uploaded = client.uploadFile(UploadFileParams.builder()17        .file(new FileParam("form.pdf", input, "application/pdf"))18        .build());1920    // 2. Flatten every form field21    PdfGateDocument flattened = client.flattenPdf(FlattenPdfParams.builder()22        .documentId(uploaded.getId())23        .build());2425    // 3. Download the flattened PDF26    byte[] pdf = client.getFile(GetFileParams.builder()27        .documentId(flattened.getId())28        .build());29    Files.write(Path.of("flattened.pdf"), pdf);30    System.out.println("Flattened " + flattened.getId() + " (" + flattened.getSize() + " bytes)");31  }32}
  • Each request has a builder, so FlattenPdfParams.builder().documentId(id).build() is a complete request and unused options are simply not set.
  • uploadFile takes a FileParam made of a file name, the bytes and a MIME type. The name only labels the multipart part.
  • flattenPdf returns a PdfGateDocument. getId() is the new document, getSize() its length, and getDerivedFrom() an Optional with the upload's ID.
  • getFile returns byte[], which is fine for forms of a few megabytes.
  • Every method declares IOException. API errors arrive as PdfGateException, which adds the HTTP status and the response body.

Against the two-page form from the fillable fields benchmark, this listing reduces 12 fields to none and leaves every value in place.

Flatten only some fields

Give fieldNames a list and only those fields are flattened. They become static content and leave the form, while the others keep working. Three fields are locked below and nine remain editable.

1PdfGateDocument partial = client.flattenPdf(FlattenPdfParams.builder()2    .documentId(uploaded.getId())3    .fieldNames(List.of("full_name", "customer_id", "start_date"))4    .build());5Files.write(Path.of("partially-flattened.pdf"), client.getFile(GetFileParams.builder()6    .documentId(partial.getId())7    .build()));8System.out.println("Partially flattened " + partial.getId());
  • The names are the ones stored in the PDF, usually the name attributes of the original inputs. extractPdfFormData returns a JsonObject keyed by them.
  • A name that is not in the document fails the whole request with a 422 that names the missing fields, and nothing is flattened.
  • An unsigned signature field has no appearance to draw and is removed cleanly.

Fill a form from HTML, then flatten

Many Java services never receive a form at all. They create one, an invoice from a Thymeleaf template or a contract from a record. In that situation nothing needs filling. Generate the HTML with the values already in the inputs, enable enableFormFields so the inputs become real fields, and flatten the generated document.

1String html = """2    <h1>Order confirmation</h1>3    <label>Customer <input type="text" name="customer" value="Acme GmbH"></label>4    <label>Seats <input type="number" name="seats" value="25"></label>5    <label>Plan6      <select name="plan">7        <option>Starter</option>8        <option selected>Business</option>9      </select>10    </label>11    <label><input type="checkbox" name="accept_terms" checked> Terms accepted</label>12    """;1314PdfGateDocument generated = client.generatePdf(GeneratePdfParams.builder()15    .html(html)16    .enableFormFields(true)17    .build());1819PdfGateDocument locked = client.flattenPdf(FlattenPdfParams.builder()20    .documentId(generated.getId())21    .build());22Files.write(Path.of("order-confirmation.pdf"), client.getFile(GetFileParams.builder()23    .documentId(locked.getId())24    .build()));25System.out.println("Generated " + generated.getId() + " and flattened it as " + locked.getId());

The value, selected and checked attributes carry over as field values and end up as fixed text and a ticked checkbox once flattened. Text blocks need Java 15 or later, so on Java 8 or 11 assemble the HTML with a StringBuilder or a template engine. Skip the flatten call if someone else should complete the form first. The supported inputs and their styling are in the fillable fields guide.

Without the SDK (java.net.http)

The SDK calls POST /forms/flatten, and that endpoint will take the PDF as a multipart part, so a single request can replace the upload and flatten pair. The JDK's HttpClient has no multipart helper, which is why the body is written out by hand below.

1import java.net.URI;2import java.net.http.HttpClient;3import java.net.http.HttpRequest;4import java.net.http.HttpResponse;5import java.nio.charset.StandardCharsets;6import java.nio.file.Files;7import java.nio.file.Path;8import java.io.ByteArrayOutputStream;910public class FlattenPdfHttp {11  public static void main(String[] args) throws Exception {12    String apiKey = System.getenv("PDFGATE_API_KEY");13    String boundary = "----pdfgate" + System.currentTimeMillis();1415    ByteArrayOutputStream body = new ByteArrayOutputStream();16    // The PDF to flatten17    body.write(("--" + boundary + "\r\n"18        + "Content-Disposition: form-data; name=\"file\"; filename=\"form.pdf\"\r\n"19        + "Content-Type: application/pdf\r\n\r\n").getBytes(StandardCharsets.UTF_8));20    body.write(Files.readAllBytes(Path.of("form.pdf")));21    // Optional: flatten only these fields22    body.write(("\r\n--" + boundary + "\r\n"23        + "Content-Disposition: form-data; name=\"fieldNames\"\r\n\r\n"24        + "[\"full_name\", \"email\"]"25        + "\r\n--" + boundary + "--\r\n").getBytes(StandardCharsets.UTF_8));2627    HttpRequest request = HttpRequest.newBuilder()28        .uri(URI.create("https://api.pdfgate.com/forms/flatten"))29        .header("Authorization", "Bearer " + apiKey)30        .header("Content-Type", "multipart/form-data; boundary=" + boundary)31        .POST(HttpRequest.BodyPublishers.ofByteArray(body.toByteArray()))32        .build();3334    HttpResponse<byte[]> response = HttpClient.newHttpClient()35        .send(request, HttpResponse.BodyHandlers.ofByteArray());3637    if (response.statusCode() != 201) {38      System.out.println(new String(response.body(), StandardCharsets.UTF_8));39      return;40    }41    Files.write(Path.of("flattened-http.pdf"), response.body());42    System.out.println("Saved flattened-http.pdf");43  }44}
  • Send file or documentId, not both. With an ID, a JSON body replaces the multipart one.
  • fieldNames is optional. In multipart form it travels as a JSON array inside a string field.
  • The success status is 201 and the body is the PDF. jsonResponse true returns the record instead, and preSignedUrlExpiresIn adds a temporary link to it.
  • test_ keys must target api-sandbox.pdfgate.com. The SDK chooses the host, hand-written HTTP does not.

In a Spring Boot controller

In a web application the flattened PDF usually goes straight back to the caller. Keep one PdfGate client, which is thread-safe and reuses its connections, and return the bytes from getFile in a ResponseEntity with the PDF media type.

1import com.pdfgate.FlattenPdfParams;2import com.pdfgate.GetFileParams;3import com.pdfgate.PdfGate;4import com.pdfgate.PdfGateDocument;5import java.io.IOException;6import org.springframework.http.HttpHeaders;7import org.springframework.http.MediaType;8import org.springframework.http.ResponseEntity;9import org.springframework.web.bind.annotation.PathVariable;10import org.springframework.web.bind.annotation.PostMapping;11import org.springframework.web.bind.annotation.RestController;1213@RestController14public class FlattenController {15  private final PdfGate client = new PdfGate(System.getenv("PDFGATE_API_KEY"));1617  @PostMapping(value = "/documents/{id}/flatten", produces = MediaType.APPLICATION_PDF_VALUE)18  public ResponseEntity<byte[]> flatten(@PathVariable String id) throws IOException {19    PdfGateDocument flattened = client.flattenPdf(FlattenPdfParams.builder()20        .documentId(id)21        .build());22    byte[] pdf = client.getFile(GetFileParams.builder()23        .documentId(flattened.getId())24        .build());25    return ResponseEntity.ok()26        .header(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=\"flattened.pdf\"")27        .body(pdf);28  }29}

Declaring throws IOExceptionlets Spring's exception handling turn API errors into a response. In a real service you would catch PdfGateException, map it to a 502 or pass its message through, depending on what the caller should see. Move the client into a @Bean if more than one controller needs it.

Notes and troubleshooting

  • Values missing after flattening. Only stored appearances are drawn. When a form was filled without regenerating them, the PDFBox NeedAppearances situation, there is nothing to draw. Fill with a tool that updates appearances, like the free fill PDF form tool, or generate from HTML as shown above.
  • Signed files. Flattening removes fields, which breaks a digital signature, so do it before signing. Documents finished through the Digital Signature API are returned flattened already.
  • Limits. Files up to 500 MB can be uploaded. The flatten call counts as a request against the plan, the upload does not.
  • Sandbox PDFs carry a PDFGate watermark and behave like production in every other way.
  • Pre-signed URLs need file storage switched on for the account, otherwise the API replies 422.

Every parameter and the response format are in the Flatten PDF API reference, the rest of the client, including the async variants, in the Java SDK documentation. The same walkthrough exists for C# and Python.

Frequently asked questions

Can I flatten a PDF in Java without PDFBox or iText?

Yes. The PDFGate Flatten PDF endpoint accepts the PDF as a multipart upload and returns the flattened PDF in the response body, so a java.net.http request is enough. The Java SDK on Maven Central wraps the same endpoint in three calls.

Why do my field values disappear when I flatten with PDFBox?

PDFBox flattens whatever appearance stream each field has. If the form was filled without regenerating appearances, the field has a value but nothing to draw. The API draws the stored appearance too, so fill forms with a tool that updates appearances, or generate the PDF from HTML with the values already in the inputs.

Can I flatten only some fields and keep the rest editable?

Yes. Pass the field names to fieldNames on the builder. Those fields become static content and the rest of the form stays interactive. If a name does not exist in the document the API returns a 422 that lists the missing names.

Which Java versions does the SDK support?

Every LTS JDK from 8 to 25. The text block syntax in the HTML example needs Java 15 or later, so on older versions build the HTML with a StringBuilder or a template.

Compress, protect, watermark and extract sit beside flatten. The Flatten PDF API page links to all of them.

This website uses cookies to offer a better browsing experience. By clicking on the "Accept" button you accept our Cookie policy.