PDFPipe

Guide

HTML to PDF in Spring Boot

PDF generation in Spring Boot historically meant Flying Saucer (limited CSS, no JavaScript), iText (AGPL licensing complexity), or PDFBox (programmatic construction only). PDFPipe is a single HTTP call from your controller or service layer: you send HTML, you get a fully rendered PDF back. No new dependencies, no license review, no browser binary to manage.

With RestTemplate (synchronous)

If you are using spring-boot-starter-web, RestTemplate is already available. Set your API key in application.properties as pdfpipe.api-key=pp_live_… and add a controller:

java
// No extra dependencies — spring-boot-starter-web includes RestTemplate.
@RestController
@RequestMapping("/api")
public class InvoiceController {

    private final RestTemplate restTemplate = new RestTemplateBuilder()
        .setConnectTimeout(Duration.ofSeconds(10))
        .setReadTimeout(Duration.ofSeconds(60))
        .build();

    @Value("${pdfpipe.api-key}")
    private String apiKey;

    @GetMapping("/invoice/{orderId}.pdf")
    public ResponseEntity<byte[]> invoice(@PathVariable String orderId) {
        String html = """
            <html>
            <body style="font-family: system-ui; padding: 40px">
              <h1>Invoice #%s</h1>
              <p>Thank you for your order.</p>
            </body>
            </html>
            """.formatted(orderId);

        HttpHeaders headers = new HttpHeaders();
        headers.set("Authorization", "Bearer " + apiKey);
        headers.setContentType(MediaType.APPLICATION_JSON);

        Map<String, Object> body = Map.of(
            "html", html,
            "options", Map.of("format", "A4")
        );

        ResponseEntity<byte[]> upstream = restTemplate.exchange(
            "https://api.pdfpipe.xyz/v1/pdf",
            HttpMethod.POST,
            new HttpEntity<>(body, headers),
            byte[].class
        );

        return ResponseEntity.ok()
            .contentType(MediaType.APPLICATION_PDF)
            .header("Content-Disposition", "attachment; filename=invoice-" + orderId + ".pdf")
            .body(upstream.getBody());
    }
}

With WebClient (reactive)

WebClient from spring-boot-starter-webflux makes a non-blocking call, fitting naturally into a reactive Spring application:

java
// spring-boot-starter-webflux includes WebClient for reactive/non-blocking calls.
@RestController
@RequestMapping("/api")
public class InvoiceController {

    private final WebClient webClient = WebClient.builder()
        .baseUrl("https://api.pdfpipe.xyz")
        .build();

    @Value("${pdfpipe.api-key}")
    private String apiKey;

    @GetMapping("/invoice/{orderId}.pdf")
    public Mono<ResponseEntity<byte[]>> invoice(@PathVariable String orderId) {
        String html = buildInvoiceHtml(orderId); // your template method

        Map<String, Object> body = Map.of(
            "html", html,
            "options", Map.of("format", "A4")
        );

        return webClient.post()
            .uri("/v1/pdf")
            .header("Authorization", "Bearer " + apiKey)
            .contentType(MediaType.APPLICATION_JSON)
            .bodyValue(body)
            .retrieve()
            .onStatus(HttpStatusCode::isError, res ->
                Mono.error(new ResponseStatusException(HttpStatus.BAD_GATEWAY, "render failed")))
            .toEntity(byte[].class)
            .map(res -> ResponseEntity.ok()
                .contentType(MediaType.APPLICATION_PDF)
                .header("Content-Disposition",
                    "attachment; filename=invoice-" + orderId + ".pdf")
                .body(res.getBody()));
    }
}

With Thymeleaf and document storage

Render your Thymeleaf template to an HTML string on the server, pass it to PDFPipe with store: true, and get a hosted download URL back. Useful for sending PDFs by email without attaching large files:

java
// Render a Thymeleaf template to an HTML string, then call PDFPipe.
// spring-boot-starter-thymeleaf is a compile dependency; no extra PDF lib needed.
@Service
public class PdfService {

    private final TemplateEngine templateEngine;
    private final RestTemplate restTemplate;

    @Value("${pdfpipe.api-key}")
    private String apiKey;

    public byte[] renderInvoice(Order order) {
        // Render the Thymeleaf template to a string.
        Context ctx = new Context();
        ctx.setVariable("order", order);
        String html = templateEngine.process("invoice", ctx);

        HttpHeaders headers = new HttpHeaders();
        headers.set("Authorization", "Bearer " + apiKey);
        headers.setContentType(MediaType.APPLICATION_JSON);

        Map<String, Object> body = Map.of(
            "html", html,
            "options", Map.of("format", "A4"),
            "store", true,
            "filename", "invoice-" + order.getId() + ".pdf"
        );

        ResponseEntity<byte[]> res = restTemplate.exchange(
            "https://api.pdfpipe.xyz/v1/pdf",
            HttpMethod.POST,
            new HttpEntity<>(body, headers),
            byte[].class
        );

        // Store the document URL for email delivery.
        String docUrl = res.getHeaders().getFirst("X-PDFPipe-Document-Url");
        order.setDocumentUrl(docUrl);
        return res.getBody();
    }
}

Getting an API key

The Hobby plan gives 500 free documents a month with no credit card. Paid plans start at $19 and include longer document retention, higher limits, and email support.

Full API reference on the docs page. Try it now in the live playground with no signup.

Get an API key →