🏠Spring FrameworkGenerate PDF files with Spring Boot using ITextPDF

Generate PDF files with Spring Boot using ITextPDF

Spring Framework

In this post, we will discuss on 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 a HTML+CSS into PDF. The methods provided by this library is 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 lets 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 to the styles.css file. You can use your own. With these in place, Lets 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.

HTML to PDF using Java and ITEXT PDF

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 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.

Sample HTML view to be converted to PDF

To convert this MVC response into PDF, you can simply takeover 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);

    }

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.

Spring Boot PDF generation using ITEXT PDF and Thymeleaf

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);             

That’s all I have on this topic for now. The sample is available in the github repository.

Similar Posts

Leave a Reply

Your email address will not be published. Required fields are marked *

8 Comments

  1. Hello! First of all thanks for the nice tutorial! When I generating a pdf with external CSS(from bootstrap) I get some exceptions and errors that do not stop the application. Exceptions live “Unsupported pseudo css selector”. Any insight would be appreciated!

    1. Some of the CSS selectors are not supported in ItextPDF. For example, Animation keyframes, focus related selections are not possible in PDF. So the CSS parser will throw errors for these. But you could safely ignore these errors by disabling the logging for CssRuleSetParser classes. Add the following property to the application.properties and you should be good.

      logging.level.com.itextpdf.styledxmlparser.css.parse.CssRuleSetParser=off
      
      1. Thank you for the answer, Raja Anbazhagan! I have one more question: can I specify folder to download the file? Thank you in advance!

        1. When you are downloading a file from a WEB URL, Your browser decides where the file should be downloaded not the server. So you pretty much can’t control this behaviour From the server-side.

          You could get more info on how to change the browser download directory in the following links.

          Chrome: https://support.google.com/chrome/answer/95759?co=GENIE.Platform%3DDesktop&hl=en
          IE: https://support.microsoft.com/en-us/topic/download-files-from-the-web-abb92c09-af3a-bd99-d279-a89848b54b0b
          Firefox: https://support.mozilla.org/en-US/kb/where-find-and-manage-downloaded-files-firefox

    1. It doesn’t matter what template engine you use… as long as you have a HTML string its fine.

      just replace the below segment to whatever you want.

              WebContext context = new WebContext(request, response, servletContext);
              context.setVariable("orderEntry", order);
              String orderHtml = templateEngine.process("order", context);
      
  2. Hi I tried using this method but my pdf is blank and only returning the name of the template

    1. TemplateEngine templateEngine = new TemplateEngine();

      Don`t forget specify this settings for your template engine
      ClassLoaderTemplateResolver templateResolver = new ClassLoaderTemplateResolver();
      templateResolver.setPrefix("templates/mail/");
      templateResolver.setSuffix(".html");
      templateResolver.setTemplateMode(TemplateMode.HTML);
      templateResolver.setCharacterEncoding("UTF-8");
      templateResolver.setOrder(0);

      templateEngine.setTemplateResolver(templateResolver);