Generate PDF document using JasperReports and Spring boot

Java featured image

1. Overview

Almost every JAVA project need to generate PDF documents for its users, for example:

  • For an e-commerce project, we generate Invoice, receipt and return…
  • For a supervision and monitoring application, we generate reports.

The JasperReports is one of the best Java libraries for generating PDF documents.

In this article, I will show you how to generate PDF documents using JasperReports, Spring Boot and Jaspersoft Studio.

2. Implementation

Let’s suppose we are working on an e-commerce application (e.g. Hybris), and we want to generate the invoice as a PDF for our customers.

This is a simplified version of how we are going to set up the generation of the invoice using JasperReports.

Overview JasperReports project

 

  • I18n-fr.properties, i18n-en.properties, i18n.properties : is an i18n resource bundles used to translate the generated pdf file.
  • Template.jrxml : is a JasportReports template created using Jaspersoft Studio.
  • Invoice.pdf : is the final pdf document generated with the help of JasperReports.

2.1. JasperReports Template.

First of all, we need to create the JasperReports template of our invoice PDF document.

1. Download and install the Jaspersoft Studio.

2. Open the Jaspersoft Studio and create a JasperReports template using the toolbox that comes with it.

I have already generate a JasperReports template, you can find it in Github.

The JasperReports template is an XML file with extension .jrxml.

Jaspersoft studio jrxml template

Let’s shed light on some of the JRXML elements :

<image>
    <reportElement x="0" y="0" width="160" height="50" uuid="e2b4ea04-ecb9-494d-a2af-63639b22b1cb"/>
    <imageExpression><![CDATA[$P{logo}]]></imageExpression>
</image>

This code snippet will add an image to the final pdf file, <![CDATA[$P{logo}]]> this parameter will be filled in Java with path to the image.

<textField>
    <reportElement x="0" y="51" width="380" height="19" uuid="76193d2c-2228-4944-b5d1-999cd7ef6168"/>
    <textElement verticalAlignment="Top">
        <font fontName="Arial" size="11" isBold="false"/>
    </textElement>
    <textFieldExpression><![CDATA[$P{order}.getAddress().getStreetName()]]></textFieldExpression>
</textField>

This code snippet adds a dynamic attribute to the pdf file, it will be filled in runtime with proper value:$P{order}.getAddress().getStreetName()

<textField>
    <reportElement x="0" y="0" width="380" height="30" uuid="932e4bce-f54c-492c-ae77-a55089c9eb9f"/>
    <textElement verticalAlignment="Middle">
        <font fontName="Arial Black" size="13" isBold="true"/>
    </textElement>
    <textFieldExpression><![CDATA[$R{jasper.invoice.address.label}]]></textFieldExpression>
</textField>

This one is similar to the last one, however this time the field value will be retrieved from the i18n resource bundle depending on the chosen languages (fr, en, it…).

2.2. Create i18n Resource Bundle

1. Create a file property for each language you need.

2. Extract all the static field from your jrxml template and add them (translated) to your files properties.

## i18n.properties (default and fullback) ##
jasper.invoice.title.label=Invoice
jasper.invoice.address.label=Shipping Address
jasper.invoice.entry.product.label=Product Name
jasper.invoice.entry.price.label=Unit Price
jasper.invoice.entry.quantity.label=Quantity
jasper.invoice.entry.total.label=Total
jasper.invoice.total.label=Order Total
## i18n_en.properties (for English) ##
jasper.invoice.title.label=Invoice
jasper.invoice.address.label=Shipping Address
jasper.invoice.entry.product.label=Product Name
jasper.invoice.entry.price.label=Unit Price
jasper.invoice.entry.quantity.label=Quantity
jasper.invoice.entry.total.label=Total
jasper.invoice.total.label=Order Total
## i18n.properties (for French) ##
jasper.invoice.title.label=Facture
jasper.invoice.address.label=Adresse de facturation
jasper.invoice.entry.product.label=Nom de produit
jasper.invoice.entry.price.label=Prix unitaire
jasper.invoice.entry.quantity.label=Quantite
jasper.invoice.entry.total.label=Total
jasper.invoice.total.label=Total de la facture

2.3. Create Spring Boot Project

1. Create a Spring Boot project using Spring Initializr.

2. Add the JasperReports and the Spring Support artifacts to your pom.xml.

  • JasperReports artifact
<dependency>
    <groupId>net.sf.jasperreports</groupId>
    <artifactId>jasperreports</artifactId>
    <version>6.6.0</version>
</dependency>
  • Spring Support artifact
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-support</artifactId>
    <version>2.0.8</version>
</dependency>

3. Add the jrxml template, the i18n files properties and images… to the resources folder of your Spring Boot project.

Jasperreport project structure

2.4. Generate PDF in Java

1. Create a Java service InvoiceService, we will use it to generate the PDF invoice.

@Service
public class InvoiceService {

	// OrderModel is a POJO contains all the data about the Invoice
	// Locale is used to localize the PDF file (French, English...)
    public void generateInvoiceFor(OrderModel order, Locale locale) throws IOException {

        // We will generate the PDF here 
        
    }
}

OrderModel is a POJO object that holds all the data about the invoice, you can find it in Github.

2. Create a PDF File and initiate a FileOutputStream.

@Service
public class InvoiceService {
	
	Logger log = LogManager.getLogger(InvoiceService.class);

    public void generateInvoiceFor(OrderModel order, Locale locale) throws IOException {

        // Create a temporary PDF file
        File pdfFile = File.createTempFile("my-invoice", ".pdf"); 
        
        // Initiate a FileOutputStream
        try(FileOutputStream pos = new FileOutputStream(pdfFile))
        {
			// We will generate PDF here
        }
        catch (final Exception e)
        {
            log.error(String.format("An error occured during PDF creation: %s", e));
        }
        
    }
}

3. Load the JRXML template.

@Service
public class InvoiceService {
	
	Logger log = LogManager.getLogger(InvoiceService.class);
	
	// Path to the jrxml template
	private final String invoice_template_path = "/jasper/invoice_template.jrxml";

    public void generateInvoiceFor(OrderModel order, Locale locale) throws IOException {

        File pdfFile = File.createTempFile("my-invoice", ".pdf"); 
        
        try(FileOutputStream pos = new FileOutputStream(pdfFile))
        {
			// Load the invoice jrxml template.
            final JasperReport report = loadTemplate();
            
        }
        catch (final Exception e)
        {
            log.error(String.format("An error occured during PDF creation: %s", e));
        }
    }
    
     // Load invoice jrxml template
    private JasperReport loadTemplate() throws JRException {

        log.info(String.format("Invoice template path : %s", invoice_template_path));

        final InputStream reportInputStream = getClass().getResourceAsStream(invoice_template_path);
        final JasperDesign jasperDesign = JRXmlLoader.load(reportInputStream);

        return JasperCompileManager.compileReport(jasperDesign);
    }
}

4. Fill the parameters and the DataSource.

@Service
public class InvoiceService {
	
	Logger log = LogManager.getLogger(InvoiceService.class);
	
	private static final String logo_path = "/jasper/images/stackextend-logo.png";
	private final String invoice_template_path = "/jasper/invoice_template.jrxml";

    public void generateInvoiceFor(OrderModel order, Locale locale) throws IOException {

        File pdfFile = File.createTempFile("my-invoice", ".pdf"); 
        
        try(FileOutputStream pos = new FileOutputStream(pdfFile))
        {
			// Load the invoice jrxml template.
            final JasperReport report = loadTemplate();
            
              // Create parameters map.
            final Map<String, Object> parameters = parameters(order, locale);

            // Create an empty datasource.
            final JRBeanCollectionDataSource dataSource = new JRBeanCollectionDataSource(Collections.singletonList("Invoice"));
            
        }
        catch (final Exception e)
        {
            log.error(String.format("An error occured during PDF creation: %s", e));
        }
    }
        
    // Fill template order parametres
    private Map<String, Object> parameters(OrderModel order, Locale locale) {
        final Map<String, Object> parameters = new HashMap<>();

        parameters.put("logo", getClass().getResourceAsStream(logo_path));
        parameters.put("order",  order);
        parameters.put("REPORT_LOCALE", locale);

        return parameters;
    }
    
    // ...
}

5. Generate the PDF using the JasperReportsUtils of the Spring Support.

@Service
public class InvoiceService {
	
	Logger log = LogManager.getLogger(InvoiceService.class);
	
	private static final String logo_path = "/jasper/images/stackextend-logo.png";
	private final String invoice_template_path = "/jasper/invoice_template.jrxml";

    public void generateInvoiceFor(OrderModel order, Locale locale) throws IOException {

        File pdfFile = File.createTempFile("my-invoice", ".pdf"); 
        
        try(FileOutputStream pos = new FileOutputStream(pdfFile))
        {
			// Load the invoice jrxml template.
            final JasperReport report = loadTemplate();
            
              // Create parameters map.
            final Map<String, Object> parameters = parameters(order, locale);

            // Create an empty datasource.
            final JRBeanCollectionDataSource dataSource = new JRBeanCollectionDataSource(Collections.singletonList("Invoice"));
            
            // Render the PDF file
            JasperReportsUtils.renderAsPdf(report, parameters, dataSource, pos);
            
        }
        catch (final Exception e)
        {
            log.error(String.format("An error occured during PDF creation: %s", e));
        }
    }
            
    // ...
}

6. Call your service InvoiceService.generateInvoiceFor(...) with the proper values to generate the PDF.

@SpringBootApplication
public class GeneratePdfDocumentApplication implements CommandLineRunner {

    Logger log = LogManager.getLogger(GeneratePdfDocumentApplication.class);

	@Resource
	private OrderService orderService;
	@Resource
    private InvoiceService invoiceService;

	@Override
	public void run(String... args) throws Exception {
		
        log.info("Start invoice generation...");

        OrderModel order = orderService.getOrderByCode("XYZ123456789");

        invoiceService.generateInvoiceFor(order, Locale.FRANCE);

        log.info("Invoice generated successfully...");
	}
	
	// ...
}

7. If everything goes well the invoice as a PDF file will be generated.

Generated invoice pdf using JasperReports

 

Demo of the final version

 

Find the source code of this example in GitHub.

 

5 3 votes
Article Rating
Subscribe
Notify of
guest

This site uses Akismet to reduce spam. Learn how your comment data is processed.

12 Comments
Oldest
Newest Most Voted
Inline Feedbacks
View all comments
bhavna
bhavna
4 years ago

most insightful tutorial on this topic! thanks

veeraraghava
veeraraghava
4 years ago

how to call deafault method our run method in commandline runner interface as rest controller in requestmapping

zako
zako
4 years ago

thx , just this method doesnt works on my eclipse , final JasperReport report = loadTemplate();

Koukou
Koukou
4 years ago

Comment ajouter le fichier. Jrxml dans sts, mon sts ne lut pas le fichier

Mohamed BELMAHI
Admin
4 years ago

test

Zotrur
Zotrur
4 years ago

The Render pdf (JasperReportsUtils) with Spring framework 5 doesn’t work more. How can I change ?

Avneesh Saini
Avneesh Saini
Reply to  Zotrur
1 year ago

You are directly use JasperFillManager and JasperExportManager as shared below:

final JasperPrint jasperPrint = JasperFillManager.fillReport(report, parameters, new JREmptyDataSource());
JasperExportManager.exportReportToPdfFile(jasperPrint, pdfFile.getAbsolutePath());

Kanagalingam J
Kanagalingam J
3 years ago

How to load custom font in Jasper ?

xyz
xyz
Reply to  Kanagalingam J
3 years ago

You may use iReport or Jasper studio and install the fonts and generate their extensions and use them in your project.

deepak
deepak
3 years ago

I got this error

net.sf.jasperreports.engine.util.JRFontNotFoundException: Font “Arial Rounded MT Bold” is not available to the JVM. See the Javadoc for more details.
at net.sf.jasperreports.engine.fonts.FontUtil.checkAwtFont(FontUtil.java:604) ~[jasperreports-6.6.0.jar:6.6.0]
at net.sf.jasperreports.engine.fill.SimpleTextLineWrapper.loadFont(SimpleTextLineWrapper.java:384) ~[jasperreports-6.6.0.jar:6.6.0]
at net.sf.jasperreports.engine.fill.SimpleTextLineWrapper.getGeneralFontInfo(SimpleTextLineWrapper.java:354) ~[jasperreports-6.6.0.jar:6.6.0]
at net.sf.jasperreports.engine.fill.SimpleTextLineWrapper.createFontInfo(SimpleTextLineWrapper.java:294) ~[jasperreports-6.6.0.jar:6.6.0]
at net.sf.jasperreports.engine.fill.SimpleTextLineWrapper.start(SimpleTextLineWrapper.java:256) ~[jasperreports-6.6.0.jar:6.6.0]
at net.sf.jasperreports.engine.fill.TextMeasurer.measure(TextMeasurer.java:543) ~[jasperreports-6.6.0.jar:6.6.0]
at net.sf.jasperreports.engine.fill.JRFillTextElement.chopTextElement(JRFillTextElement.java:665) ~[jasperreports-6.6.0.jar:6.6.0]
at net.sf.jasperreports.engine.fill.JRFillTextField.prepare(JRFillTextField.java:784) ~[jasperreports-6.6.0.jar:6.6.0]
at net.sf.jasperreports.engine.fill.JRFillElementContainer.prepareElements(JRFillElementContainer.java:542) ~[jasperreports-6.6.0.jar:6.6.0]
at net.sf.jasperreports.engine.fill.JRFillFrame.prepare(JRFillFrame.java:241) ~[jasperreports-6.6.0.jar:6.6.0]
at net.sf.jasperreports.engine.fill.JRFillElementContainer.prepareElements(JRFillElementContainer.java:542) ~[jasperreports-6.6.0.jar:6.6.0]
at net.sf.jasperreports.engine.fill.JRFillBand.fill(JRFillBand.java:453) ~[jasperreports-6.6.0.jar:6.6.0]
at net.sf.jasperreports.engine.fill.JRFillBand.fill(JRFillBand.java:428) ~[jasperreports-6.6.0.jar:6.6.0]
at net.sf.jasperreports.engine.fill.JRVerticalFiller.fillBandNoOverflow(JRVerticalFiller.java:448) ~[jasperreports-6.6.0.jar:6.6.0]
at net.sf.jasperreports.engine.fill.JRVerticalFiller.fillColumnHeader(JRVerticalFiller.java:496) ~[jasperreports-6.6.0.jar:6.6.0]
at net.sf.jasperreports.engine.fill.JRVerticalFiller.fillReportStart(JRVerticalFiller.java:260) ~[jasperreports-6.6.0.jar:6.6.0]
at net.sf.jasperreports.engine.fill.JRVerticalFiller.fillReport(JRVerticalFiller.java:110) ~[jasperreports-6.6.0.jar:6.6.0]
at net.sf.jasperreports.engine.fill.JRBaseFiller.fill(JRBaseFiller.java:615) ~[jasperreports-6.6.0.jar:6.6.0]
at net.sf.jasperreports.engine.fill.BaseReportFiller.fill(BaseReportFiller.java:432) ~[jasperreports-6.6.0.jar:6.6.0]
at net.sf.jasperreports.engine.fill.JRFillSubreport.fillSubreport(JRFillSubreport.java:818) ~[jasperreports-6.6.0.jar:6.6.0]
at net.sf.jasperreports.engine.fill.JRSubreportRunnable.run(JRSubreportRunnable.java:61) ~[jasperreports-6.6.0.jar:6.6.0]
at net.sf.jasperreports.engine.fill.AbstractThreadSubreportRunner.run(AbstractThreadSubreportRunner.java:221) ~[jasperreports-6.6.0.jar:6.6.0]
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149) ~[na:1.8.0_191]
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624) ~[na:1.8.0_191]
at java.lang.Thread.run(Thread.java:748) [na:1.8.0_191]

Valsaraj Viswanathan
Valsaraj Viswanathan
Reply to  deepak
2 years ago

Download and place the font in jre/lib/fonts folder to fix.

Eqbal
Eqbal
2 years ago

How to attach new Jasper report without modifying Spring Boot application to call new jasper repot. I mean to make the Report dynamic ,

12
0
Would love your thoughts, please comment.x
()
x