7 min read

Generate PDF reports in Spring Boot using ITextPDF.

September 27, 2020

Generating PDF files with spring boot has never been this easier. In this post, we will discuss on how to create dynamic PDF files using itextpdf library and thymeleaf.

Understand ItextPdf

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.

Crafting PDF by Hand

To start with, We need the following maven dependency.

        <dependency>
            <groupId>com.itextpdf</groupId>
            <artifactId>kernel</artifactId>
            <version>7.1.12</version>
        </dependency>

The itextpdf-kernal is the core library that comes with Some basic classes Like Paragraph, Text, Image, List, Tables etc. These representations of PDF building blocks can take styling options like font-size, colour and Padding and margin etc. To try these out, lets create a simple PDF file with some content image and a table.

        PdfDocument pdfDoc = new PdfDocument(new PdfWriter("demo.pdf",
                new WriterProperties().addUAXmpMetadata().setPdfVersion(PdfVersion.PDF_1_7)));
        Document document = new Document(pdfDoc, PageSize.A4.rotate());
        pdfDoc.getCatalog().setViewerPreferences(new PdfViewerPreferences().setDisplayDocTitle(true));
        pdfDoc.getCatalog().setLang(new PdfString("en-IN"));
        pdfDoc.getDocumentInfo().setTitle("SpringHow Tutorials");

Now, let’s add a paragraph with an image inside.

        Paragraph p = new Paragraph();
        p.setFontSize(18);
        p.add("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. ");

        Image img = new Image(ImageDataFactory.create("photo.jpg"));
        img.getAccessibilityProperties().setAlternateDescription("Orange");
        p.add(img);

        p.add("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. ");

        document.add(p);

Now a table with four columns at the end of the document.

        Table table = new Table(4);
        table.setWidth(UnitValue.createPercentValue(100));
        table.addHeaderCell("Product");
        table.addHeaderCell("QTY");
        table.addHeaderCell("Price");
        table.addHeaderCell("Total");
        table.addCell("Jeans");
        table.addCell("2");
        table.addCell("10.99");
        table.addCell("20.98");
        table.addCell("Shirt");
        table.addCell("2");
        table.addCell("7.99");
        table.addCell("14.98");
        document.add(table);

Note that it is plain old Java code using the said libraries. The github link for this source is available at the end of this post. Let’s close the document with document.close() and run our code. Here is the sample output. pdf by hand crafted code

As you could see, The code had become large even without a lot of formatting. It will be impossible to tweak the styles for some of the texts without doing some debugging. This is where html2pdf comes in to picture.

Generating PDF from HTML

The Itext library comes with a supporting library called html2pdf that can convert Html and CSS into visually pleasing PDF documents. Unlike the first method, This needs very less code. Here is how.

First add the html2pdf converter library along with the core library.

        <dependency>
            <groupId>com.itextpdf</groupId>
            <artifactId>html2pdf</artifactId>
            <version>3.0.1</version>
        </dependency>
<!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>

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.

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

This helper method takes a input html file parses it applies CSS and produces an output pdf. 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.

PDF generated from HTML

Dynamic content for PDF

Let’s face it No one wants to generate the file with same content. One would want the file to be dynamically generated based on the business logic. Take a scenario where the user may need to download order details. Each order may have different items addresses, card details etc. For this, the first approach that we saw earlier may seem like a good idea. But maintaining the code is still an issue. Up next, We will see how we can solve this problem with template engines and MVC.

Spring MVC and Thymeleaf

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>
    @RequestMapping(path = "/")
    public String getOrderPage(Model model) throws IOException {
        Order order = OrderHelper.getOrder()
        model.addAttribute("orderEntry", );
        return "order";
    }

With the above setup, Your response when hitting the controller API looks like below.

HTML generated by Thymeleaf

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

    }

The Results

I have written my controller method on /orders/pdf/ and here are the results when I hit the URL from the browser. The Generated PDF

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

  • 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 link at the end of this post.

Raja Anbazhagan

About the author

Raja is a Software Engineer with over 7 years of experience in working with Enterprise Java applications. Lately, He is focused in cloud-based Java applications and serverless technologies. He spends his spare time in stackoverflow.

Browse Categories