Java
Generate PDFs from Java applications using the Format API.
Format doesn't have a dedicated Java SDK yet, so this page outlines our recommend approach to use Format within a Java environment.
Want a dedicated SDK?
We're exploring native SDKs for additional platforms. If this is important for your team, reach out to us.
How it works
This is our recommended workflow for Java users.
Design
Create your PDF templates locally using Format Studio (React, Vue, or HTML).
Compile
Compile them down to a renderer bundle and assets zip using format compile. This can be done locally or in CI.
Deploy a render service
Deploy a lightweight Node.js service alongside your application. This service takes your data, renders the HTML from the compiled bundle, and calls the Format API to return the final PDF.
Call the render service
Your application sends data to the render service and receives a PDF back. No need to interact with the Format API directly.
The render service
Your compiled Format templates are JavaScript modules. The render service wraps them in a small HTTP server that accepts data, renders the HTML, and returns the PDF. You can use any server or serverless infrastructure that supports a Node runtime.
If your PDF has no dynamic data, you don't need a render service. You can call render() once after compile, save the resulting HTML to a file, and have your application send that static HTML directly to the Format API.
Here is a standalone example using Hono:
import { invoice } from './_generated'
import { FormatClient } from '@format.dev/client'
import { serve } from '@hono/node-server'
import { Hono } from 'hono'
const format = new FormatClient()
const app = new Hono()
app.post('/render/invoice', async (c) => {
const data = await c.req.json()
const doc = await invoice.render(data)
const response = await format.pdf(doc)
return new Response(response.body, {
headers: {
'Content-Type': 'application/pdf',
'Content-Disposition': `attachment; filename="${response.filename ?? 'document.pdf'}"`,
},
})
})
serve(app, { port: 3100 })The _generated directory (produced by format compile) is deployed alongside this service. It contains the renderer bundles and asset zips. Your application does not need access to these files.
Calling from Java
Your Java application sends the document data as JSON and receives a PDF stream back. The render service can run locally alongside your application or as a separate service in production (a different container, serverless function, etc.).
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.nio.file.Files;
import java.nio.file.Path;
String renderServiceUrl = System.getenv().getOrDefault("RENDER_SERVICE_URL", "http://localhost:3100");
String invoiceData = """
{ "customerName": "Ada Lovelace", "invoiceNo": "INV-001" }
""";
HttpClient client = HttpClient.newHttpClient();
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create(renderServiceUrl + "/render/invoice"))
.header("Content-Type", "application/json")
.POST(HttpRequest.BodyPublishers.ofString(invoiceData))
.build();
HttpResponse<byte[]> response = client.send(request, HttpResponse.BodyHandlers.ofByteArray());
if (response.statusCode() == 200) {
Files.write(Path.of("./output/invoice.pdf"), response.body());
}Calling the API directly
If you prefer to skip the render service and call the Format API directly from Java, you can do so with the built-in HttpClient.
The Format API accepts a POST request with multipart form data and returns a PDF binary stream.
POST https://api.format.dev/v1/render
Authorization: Bearer {FORMAT_API_KEY}
Content-Type: multipart/form-data| Field | Type | Description |
|---|---|---|
html | string | The rendered HTML output from your compiled template |
assets | binary (optional) | A ZIP file containing images, fonts, and static resources |
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.UUID;
String apiKey = System.getenv("FORMAT_API_KEY");
String boundary = UUID.randomUUID().toString();
byte[] assetsBytes = Files.readAllBytes(Path.of("./assets.zip"));
String body = "--" + boundary + "\r\n"
+ "Content-Disposition: form-data; name=\"html\"\r\n\r\n"
+ html + "\r\n"
+ "--" + boundary + "\r\n"
+ "Content-Disposition: form-data; name=\"assets\"; filename=\"assets.zip\"\r\n"
+ "Content-Type: application/zip\r\n\r\n";
byte[] prefix = body.getBytes();
byte[] suffix = ("\r\n--" + boundary + "--\r\n").getBytes();
byte[] requestBody = new byte[prefix.length + assetsBytes.length + suffix.length];
System.arraycopy(prefix, 0, requestBody, 0, prefix.length);
System.arraycopy(assetsBytes, 0, requestBody, prefix.length, assetsBytes.length);
System.arraycopy(suffix, 0, requestBody, prefix.length + assetsBytes.length, suffix.length);
HttpClient client = HttpClient.newHttpClient();
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create("https://api.format.dev/v1/render"))
.header("Authorization", "Bearer " + apiKey)
.header("Content-Type", "multipart/form-data; boundary=" + boundary)
.POST(HttpRequest.BodyPublishers.ofByteArray(requestBody))
.build();
HttpResponse<byte[]> response = client.send(request, HttpResponse.BodyHandlers.ofByteArray());
if (response.statusCode() == 200) {
Files.write(Path.of("./output/invoice.pdf"), response.body());
}This approach requires you to produce the HTML and assets yourself. See the API client reference for full details.