Need to generate PDFs from URLs or HTML in a Java application? Whether you're building invoice generation in Spring Boot, automated report exports, or a document archiving service, this tutorial shows you how to go from URL (or raw HTML) to a pixel-perfect PDF — no Headless Chrome to configure, no iText license to manage, no native binary dependencies.
We'll use the URLSnap REST API: send an HTTP request, receive a PDF back. Works out of the box with Java 11+'s built-in HttpClient and with Spring Boot's WebClient.
Libraries like iText require you to build your PDF layout imperatively in Java code — fine for data tables, painful for anything that looks like a real webpage. An API approach lets you render any HTML/CSS/JS exactly as a browser would, then hand you back the bytes.
Java 11 or higher (for the built-in java.net.http.HttpClient). No external dependencies needed for the basic examples. Spring WebClient and OkHttp examples require their respective libraries.
curl -X POST https://urlsnap.dev/api/register \
-H "Content-Type: application/json" \
-d '{"email":"you@example.com"}'
# {"key":"us_abc123...","message":"Free tier: 20 requests/day"}
import java.net.URI;
import java.net.URLEncoder;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.time.Duration;
public class PdfFromUrl {
private static final String API_KEY = "us_your_key_here"; // get free at urlsnap.dev
private static final String PDF_ENDPOINT = "https://urlsnap.dev/api/pdf";
public static void main(String[] args) throws Exception {
String targetUrl = "https://example.com";
String encoded = URLEncoder.encode(targetUrl, StandardCharsets.UTF_8);
HttpClient client = HttpClient.newBuilder()
.connectTimeout(Duration.ofSeconds(15))
.build();
// format=A4, landscape=false, margin=20px (all optional)
String query = "url=" + encoded + "&format=A4&landscape=false&margin=20px";
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create(PDF_ENDPOINT + "?" + query))
.header("x-api-key", API_KEY)
.timeout(Duration.ofSeconds(60))
.GET()
.build();
HttpResponse<byte[]> response = client.send(
request, HttpResponse.BodyHandlers.ofByteArray()
);
if (response.statusCode() != 200) {
throw new RuntimeException("API error " + response.statusCode() +
": " + new String(response.body()));
}
Path output = Path.of("output.pdf");
Files.write(output, response.body());
System.out.println("PDF saved to " + output.toAbsolutePath() +
" (" + response.body().length + " bytes)");
}
}
Need to generate invoices or reports from a template? POST your HTML directly and receive a rendered PDF — no public URL required.
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.time.Duration;
public class PdfFromHtml {
private static final String API_KEY = "us_your_key_here";
public static void main(String[] args) throws Exception {
String html = """
<!DOCTYPE html>
<html>
<head>
<style>
body { font-family: Arial, sans-serif; padding: 40px; color: #333; }
.header { border-bottom: 2px solid #4f46e5; padding-bottom: 20px; margin-bottom: 30px; }
.invoice-row { display: flex; justify-content: space-between; margin: 8px 0; }
.total { font-size: 1.4rem; font-weight: bold; color: #4f46e5; margin-top: 20px; }
</style>
</head>
<body>
<div class="header">
<h1>Invoice #1042</h1>
<p>Acme Corp · April 4, 2026</p>
</div>
<div class="invoice-row"><span>API Starter Plan (1 month)</span><span>$9.00</span></div>
<div class="invoice-row"><span>Tax (10%)</span><span>$0.90</span></div>
<hr>
<div class="invoice-row total"><span>Total</span><span>$9.90</span></div>
</body>
</html>
""";
// Build JSON body manually (no extra dependencies needed)
String jsonBody = "{\"html\":" + jsonString(html) + ",\"format\":\"A4\",\"margin\":\"30px\"}";
HttpClient client = HttpClient.newBuilder()
.connectTimeout(Duration.ofSeconds(15))
.build();
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create("https://urlsnap.dev/api/pdf"))
.header("x-api-key", API_KEY)
.header("Content-Type", "application/json")
.timeout(Duration.ofSeconds(60))
.POST(HttpRequest.BodyPublishers.ofString(jsonBody))
.build();
HttpResponse<byte[]> response = client.send(
request, HttpResponse.BodyHandlers.ofByteArray()
);
if (response.statusCode() != 200) {
throw new RuntimeException("Error " + response.statusCode() +
": " + new String(response.body()));
}
Files.write(Path.of("invoice.pdf"), response.body());
System.out.println("Invoice saved to invoice.pdf");
}
// Minimal JSON string escaper — use Jackson/Gson for production
static String jsonString(String s) {
return "\"" + s.replace("\\", "\\\\").replace("\"", "\\\"")
.replace("\n", "\\n").replace("\r", "\\r")
.replace("\t", "\\t") + "\"";
}
}
| Parameter | Type | Default | Description |
|---|---|---|---|
| url | string | required (GET) | Target URL to convert to PDF |
| html | string | required (POST) | Raw HTML to render as PDF |
| format | string | A4 | Paper size: A4, Letter, Legal, A3 |
| landscape | bool | false | Landscape orientation |
| margin | string | 20px | Page margin (any CSS value, e.g. 0.5in) |
import java.net.URI;
import java.net.URLEncoder;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.nio.charset.StandardCharsets;
import java.time.Duration;
/**
* Thread-safe URLSnap PDF client.
* Reuse a single instance across your application.
*/
public class UrlSnapPdfClient {
private final String apiKey;
private final HttpClient http;
private static final String BASE = "https://urlsnap.dev/api/pdf";
public UrlSnapPdfClient(String apiKey) {
this.apiKey = apiKey;
this.http = HttpClient.newBuilder()
.connectTimeout(Duration.ofSeconds(15))
.build();
}
/** Convert a public URL to PDF bytes. */
public byte[] fromUrl(String url, String pageFormat, boolean landscape, String margin)
throws Exception {
String query = "url=" + URLEncoder.encode(url, StandardCharsets.UTF_8)
+ "&format=" + pageFormat
+ "&landscape=" + landscape
+ "&margin=" + URLEncoder.encode(margin, StandardCharsets.UTF_8);
HttpRequest req = HttpRequest.newBuilder()
.uri(URI.create(BASE + "?" + query))
.header("x-api-key", apiKey)
.timeout(Duration.ofSeconds(60))
.GET().build();
return send(req);
}
/** Convert raw HTML to PDF bytes. */
public byte[] fromHtml(String html, String pageFormat, boolean landscape, String margin)
throws Exception {
String body = String.format(
"{\"html\":%s,\"format\":\"%s\",\"landscape\":%b,\"margin\":\"%s\"}",
jsonEscape(html), pageFormat, landscape, margin);
HttpRequest req = HttpRequest.newBuilder()
.uri(URI.create(BASE))
.header("x-api-key", apiKey)
.header("Content-Type", "application/json")
.timeout(Duration.ofSeconds(60))
.POST(HttpRequest.BodyPublishers.ofString(body)).build();
return send(req);
}
private byte[] send(HttpRequest req) throws Exception {
HttpResponse<byte[]> res = http.send(req, HttpResponse.BodyHandlers.ofByteArray());
if (res.statusCode() == 429)
throw new RuntimeException("Daily limit reached — upgrade at urlsnap.dev/#pricing");
if (res.statusCode() != 200)
throw new RuntimeException("PDF API error " + res.statusCode() +
": " + new String(res.body()));
return res.body();
}
private static String jsonEscape(String s) {
return "\"" + s.replace("\\", "\\\\").replace("\"", "\\\"")
.replace("\n", "\\n").replace("\r", "\\r")
.replace("\t", "\\t") + "\"";
}
}
// PdfController.java
import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
@RestController
@RequestMapping("/pdf")
public class PdfController {
private final UrlSnapPdfClient pdfClient;
public PdfController(@Value("${urlsnap.api-key}") String apiKey) {
this.pdfClient = new UrlSnapPdfClient(apiKey);
}
/** GET /pdf/from-url?url=https://example.com */
@GetMapping("/from-url")
public ResponseEntity<byte[]> fromUrl(@RequestParam String url) throws Exception {
byte[] pdf = pdfClient.fromUrl(url, "A4", false, "20px");
return ResponseEntity.ok()
.header(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=\"page.pdf\"")
.contentType(MediaType.APPLICATION_PDF)
.body(pdf);
}
/** POST /pdf/invoice — body: {"html":"<h1>...</h1>"} */
@PostMapping("/invoice")
public ResponseEntity<byte[]> invoice(@RequestBody InvoiceRequest req) throws Exception {
byte[] pdf = pdfClient.fromHtml(req.html(), "A4", false, "30px");
return ResponseEntity.ok()
.header(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=\"invoice.pdf\"")
.contentType(MediaType.APPLICATION_PDF)
.body(pdf);
}
public record InvoiceRequest(String html) {}
}
// application.properties:
// urlsnap.api-key=us_your_key_here
// After getting the PDF bytes, upload to any object store:
import software.amazon.awssdk.core.sync.RequestBody;
import software.amazon.awssdk.services.s3.S3Client;
import software.amazon.awssdk.services.s3.model.PutObjectRequest;
byte[] pdfBytes = pdfClient.fromUrl("https://example.com", "A4", false, "20px");
S3Client s3 = S3Client.create();
s3.putObject(
PutObjectRequest.builder()
.bucket("my-reports-bucket")
.key("reports/2026-04-04-report.pdf")
.contentType("application/pdf")
.build(),
RequestBody.fromBytes(pdfBytes)
);
System.out.println("Uploaded to S3");
import java.net.URI;
import java.net.http.*;
public class QuotaCheck {
public static void main(String[] args) throws Exception {
String apiKey = "us_your_key_here";
HttpClient http = HttpClient.newHttpClient();
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create("https://urlsnap.dev/api/me"))
.header("x-api-key", apiKey)
.GET().build();
HttpResponse<String> response = http.send(
request, HttpResponse.BodyHandlers.ofString()
);
System.out.println(response.body());
// {"plan":"free","requests_today":3,"daily_limit":20,"requests_total":15}
}
}
Free tier: 20 PDFs/day. No Headless Chrome, no iText, no server-side browser process.
Get your free API key →