In this post, we will discuss how to generate PDF files using Spring Boot, thymeleaf, and Itext library.
Understanding Itext PDF
The library helps generate PDF files by either creating each element manually or by converting HTML+CSS into PDF. The methods provided by this library are straightforward. Let’s see both of these methods in action.
Generating PDF from HTML
To start with, We need the following maven dependency.
<dependency>
<groupId>com.itextpdf</groupId>
<artifactId>kernel</artifactId>
<version>7.1.12</version>
</dependency>
Code language: HTML, XML (xml)
The Itext library comes with a supporting library called html2pdf that can convert Html and CSS to visually pleasing PDF documents. Unlike using Java code, this method is clean to implement. So let us add that dependency as well to our java project.
<dependency>
<groupId>com.itextpdf</groupId>
<artifactId>html2pdf</artifactId>
<version>3.0.1</version>
</dependency>
Code language: HTML, XML (xml)
<!doctype html>
<html lang="en">
<head>
<title>SpringHow html to pdf</title>
<link rel="stylesheet" href="style.css">
</head>
<body>
<div>
<p >Lorum ipsum some text before image. Lorum ipsum some text before image. Lorum ipsum some text before image. Lorum ipsum some text before image. Lorum ipsum some text before image. Lorum ipsum some text before image. Lorum ipsum some text before image. Lorum ipsum some text before image. </p>
<img src="photo.jpg" alt="Orange">
<p >Lorum ipsum some text after image. Lorum ipsum some text after image. Lorum ipsum some text after image. Lorum ipsum some text after image. Lorum ipsum some text after image. Lorum ipsum some text after image. Lorum ipsum some text after image. Lorum ipsum some text after image. Lorum ipsum some text after image.</p>
<table>
<tr><th>Product</th><th>Quantity</th><th>Price</th><th>Total</th></tr>
<tr><td>Jeans</td><td>2</td><td>10.99</td><td>20.98</td></tr>
<tr><td>Shirt</td><td>2</td><td>7.99</td><td>14.98</td></tr>
</table>
</div>
</body>
</html>
Code language: HTML, XML (xml)
Call the above HTML as pdf-input.html
.I placed appropriate styles in the styles.css file. You can use your own. With these in place, let’s call the HtmlConverter.convertToPdf
method in our Java code.
import java.io.*;
import com.itextpdf.html2pdf.HtmlConverter;
public class GeneratePDFUsingHTML {
public static void main(String[] args) throws IOException {
HtmlConverter.convertToPdf(new File("./pdf-input.html"),new File("demo-html.pdf"));
}
}
Code language: JavaScript (javascript)
This helper method takes an input HTML file parses it applies CSS and converts it to a pdf output. This approach is simple, isn’t it? It is far better to update the HTML file rather than digging into a thousand lines of code. And here is the result.
We can use the above approach anywhere in java applications to generate rich and visually pleasing PDF files. Even though this seems like a great way to create PDF files, We are still lacking the ability to create dynamic PDF files.
Generating PDF from MVC views
Spring MVC with a template engine can provide dynamic HTML content. We can easily convert these into PDF responses with the following approach. For this example, I imported spring-boot-starter-web
and spring-boot-starter-thymeleaf
for MVC and thymeleaf support to my spring boot project. You may use your own choice of template engine. Take a look at this thymeleaf template below. This will generate us the order Details. Also, I have a helper method from OrderHelper
to generate some dummy order content.
<!doctype html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<meta content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0"
name="viewport">
<meta content="ie=edge" http-equiv="X-UA-Compatible">
<title>Spring Boot - Thymeleaf</title>
<link th:href="@{/main.css}" rel="stylesheet"/>
</head>
<body class="flex items-center justify-center h-screen">
<div class="rounded-lg border shadow-lg p-10 w-3/5">
<div class="flex flex-row justify-between pb-4">
<div>
<h2 class="text-xl font-bold">Order #<span class="text-green-600" th:text="${orderEntry.orderId}"></span>
</h2>
</div>
<div>
<div class="text-xl font-bold" th:text="${orderEntry.date}"></div>
</div>
</div>
<div class="flex flex-col pb-8">
<div class="pb-2">
<h2 class="text-xl font-bold">Delivery Address</h2>
</div>
<div th:text="${orderEntry.account.address.street}"></div>
<div th:text="${orderEntry.account.address.city}"></div>
<div th:text="${orderEntry.account.address.state}"></div>
<div th:text="${orderEntry.account.address.zipCode}"></div>
</div>
<table class="table-fixed w-full text-right border rounded">
<thead class="bg-gray-100">
<tr>
<th class="text-left pl-4">Product</th>
<th>Qty</th>
<th>Price</th>
<th class="pr-4">Total</th>
</tr>
</thead>
<tbody>
<tr th:each="item : ${orderEntry.items}">
<td class="pl-4 text-left" th:text="${item.name}"></td>
<td th:text="${item.quantity}"></td>
<td th:text="${item.price}"></td>
<td class="pr-4" th:text="${item.price * item.quantity}"></td>
</tr>
</tbody>
</table>
<div class="flex flex-row-reverse p-5">
<h2 class="font-medium bg-gray-200 p-2 rounded">
Grand Total: <span class="text-green-600" th:text="${orderEntry.payment.amount}"></span>
</h2>
</div>
<h2 class="text-xl font-bold">Payment Details</h2>
<table class="table-fixed text-left w-2/6 border">
<tr>
<th class="text-green-600">Card Number</th>
<td th:text="${orderEntry.payment.cardNumber}"></td>
</tr>
<tr>
<th class="text-green-600">CVV</th>
<td th:text="${orderEntry.payment.cvv}"></td>
</tr>
<tr>
<th class="text-green-600">Expires (MM/YYYY)</th>
<td th:text="${orderEntry.payment.month +'/'+ orderEntry.payment.year}"></td>
</tr>
</table>
</div>
</body>
</html>
Code language: HTML, XML (xml)
@RequestMapping(path = "/")
public String getOrderPage(Model model) throws IOException {
Order order = OrderHelper.getOrder()
model.addAttribute("orderEntry", order);
return "order";
}
Code language: JavaScript (javascript)
With the above setup, Your response when hitting the controller API looks like below.
To convert this MVC response into PDF, you can simply take over the thymeleaf generated HTML content and convert it into PDF using HtmlConverter.
@RequestMapping(path = "/pdf")
public ResponseEntity<?> getPDF(HttpServletRequest request, HttpServletResponse response) throws IOException {
/* Do Business Logic*/
Order order = OrderHelper.getOrder();
/* Create HTML using Thymeleaf template Engine */
WebContext context = new WebContext(request, response, servletContext);
context.setVariable("orderEntry", order);
String orderHtml = templateEngine.process("order", context);
/* Setup Source and target I/O streams */
ByteArrayOutputStream target = new ByteArrayOutputStream();
/*Setup converter properties. */
ConverterProperties converterProperties = new ConverterProperties();
converterProperties.setBaseUri("http://localhost:8080");
/* Call convert method */
HtmlConverter.convertToPdf(orderHtml, target, converterProperties);
/* extract output as bytes */
byte[] bytes = target.toByteArray();
/* Send the response as downloadable PDF */
return ResponseEntity.ok()
.contentType(MediaType.APPLICATION_PDF)
.body(bytes);
}
Code language: PHP (php)
Testing Spring Boot PDF Generation
I have written my controller method on /orders/pdf/
and here are the results when I hit the URL from the browser.
Here you can see that the response is a PDF document. You can confirm the same by the look of Chrome’s PDF viewer.
Points to Note When using HTML to PDF in Spring Boot
- The
converterProperties.setBaseUri
important. Otherwise, static assets like/main.css
will be resolved from the local path. - You can note that some elements moved a bit or misaligned compared to it’s HTML counterpart. This behaviour is due to the library not supporting some CSS properties. But you can always tweak the CSS for best results.
- Keep an eye on the logs for unsupported CSS properties. For Example,
Unsupported pseudo CSS selector: :-moz-focusring
. you can safely ignore the error as long as the PDF generation will continue. - If you need the file to download on request, then you could add a disposition as shown below.
return ResponseEntity.ok()
.header(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=order.pdf")
.contentType(MediaType.APPLICATION_PDF)
.body(bytes);
Code language: JavaScript (javascript)
That’s all I have on this topic for now. The sample is available in the Github repository.