🏠Spring FrameworkGenerate PDF files with Spring Boot using ITextPDF

Generate PDF files with Spring Boot using ITextPDF

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.

html to pdf using itext pdf
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 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.

Sample HTML view to be converted to PDF
Sample HTML view to be converted to PDF

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.

generate PDF files using Spring Boot, thymeleaf and Itext PDF
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);             Code language: JavaScript (javascript)

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

Related

Similar Posts

Leave a Reply

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

17 Comments

  1. I was able to add html footer and header to each page by handling the css @page media this way:
    /***********************
    PAGE
    *********************************/
    @page{
    size: A4;
    margin-right: 30px;
    margin-left: 30px;
    @top-left {
    content: element(no-logo-header);
    }

    @top-right {
    content: element(header_title);
    }
    }

    @page :right{
    @top-left {
    content: element(header);
    }

    @bottom-center {
    content: element(footer);
    }
    }

    @page :before{
    margin-top: 4.8cm;
    margin-bottom: 2.8cm;
    }

    /***********************
    HEADER
    *********************************/
    header {
    position: running(header);
    display: block;
    width: 100%;
    }

    .no-logo-header{
    position: running(no-logo-header);
    width: 100%;
    }

    .header_title {
    position: running(header_title);
    }
    /***********************
    FOOTER
    *********************************/
    .footer{
    position: running(footer);
    height: 200px;
    width: 100%;
    }
    and in my html file:

    some content

    a different content

    as you can see, in the html file, you need the header and footer as the first elements and only then the content of the pages. In the css file, you can have specific css rules for odd, even, first pages. I was able to increase the page margins by using the pseudo element :before on @page.
    I hope this can help someone!

  2. Thank you for this great tutorial! FYI, flexbox is not supported in the generation of the pdf …
    using float:right/left, text-align: left/right and other little tricks make it possible to make the style work like I needed to

  3. Thank you for this tutorial, can you give some insights on how to add html footer and header to each page

    1. In Itext, there is no specific thing as a header/footer. But if you are using templates like in this tutorial, you can add the HTML for the header and footer and that content will come to your PDF.

  4. When I generate my pdf using spring boot I get all the text but it removes my images. Is there a way to fix this problem?

    1. If your images are being loaded from a different domain name, then that might be a problem. You should set converterProperties.setBaseUri(“https://your-domain/”); to your application URL base path to resolve the images properly.

  5. Is it possible?
    A pdf with multi orientation page, (mixed landscape and portrait) using IText.
    I see – flying saucer, which use Itext in background, can do it.

    1. Because we are using `HtmlConverter.convertToPdf()` method for our entire HTML, there is no way to specify the page size for each pages. If you are manually creating a PDF, then you could use the `PdfDocument.addPage()` method with which you can add pages of different size.

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

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