Introduction
Jasper Reports is an open-source library used to generate reports as PDFs, xlsx, HTML, CSV, and other miscellaneous formats. Jasper Reports can accept data from multiple data sources (databases, XML, json …). In this blog post, we will see how jasper reports work and showcase how you can generate pdf reports using Jasper Studio and the Jasper Reports Java library.
Workflow
When I am working to generate a pdf report with Jasper, I follow the following workflow:

First, we create the JRXML file, which is just an XML file that describes the layout and other properties of the document. An easy way to generate this file is to use Jaspersoft Studio (Successor of iReport).
Then, we take the JRXML file and compile it to a .jasper file. The compiled file is a binary. It can be compiled using either a tool like Jaspersoft Studio or at runtime using JasperCompileManager class.
After compiling the JRXML file into a .jasper file, we can link dynamic data at runtime from a data source or from our custom-defined parameters.
Finally, we export the report using the facade provided by the library.
Example Demo
The following example will show how to design and generate a pdf report with dynamic data. Before going to the IDE, we’ll need to generate the layout of our document. To do so, we’ll create a new Jasper Reports in JasperSoft Studio by going to File | New | Jasper Reports in the menu:

Continue by hitting next and hit finish. Here in the Design tab, you can see the document’s layout along with a palette that you can leverage to drag and drop elements.

In the above short video, I’ve added a text field element and a new parameter. Parameters are used at runtime by the Java program to pass dynamic data. Here, I’ve designed a simple layout with few parameters to be populated:

Now, we can see the generated JRXML in the below Source tab:
<?xml version="1.0" encoding="UTF-8"?>
<!-- Created with Jaspersoft Studio version 6.17.0.final using JasperReports Library version 6.17.0-6d93193241dd8cc42629e188b94f9e0bc5722efd -->
<jasperReport xmlns="http://jasperreports.sourceforge.net/jasperreports" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://jasperreports.sourceforge.net/jasperreports http://jasperreports.sourceforge.net/xsd/jasperreport.xsd" name="order-info" pageWidth="595" pageHeight="842" columnWidth="555" leftMargin="20" rightMargin="20" topMargin="20" bottomMargin="20" uuid="69537f82-b266-4663-9dc5-dee389aa7897">
<parameter name="firstName" class="java.lang.String"/>
<parameter name="lastName" class="java.lang.String"/>
<parameter name="price" class="java.lang.String"/>
<parameter name="orderId" class="java.lang.String"/>
<parameter name="orderDate" class="java.lang.String"/>
<parameter name="productName" class="java.lang.String"/>
<queryString>
<![CDATA[]]>
</queryString>
<background>
<band splitType="Stretch"/>
</background>
<detail>
<band height="504" splitType="Stretch">
<textField>
<reportElement x="150" y="0" width="210" height="30" uuid="bc7523d6-aeb5-4d16-83f0-129e7e030e8c"/>
<textElement textAlignment="Center" verticalAlignment="Middle">
<font size="16" isBold="true"/>
</textElement>
<textFieldExpression><![CDATA["Order Details"]]></textFieldExpression>
</textField>
<textField>
<reportElement x="0" y="41" width="480" height="30" uuid="9c79250c-8625-44bc-8fe3-e15ad3b313f9"/>
<textElement textAlignment="Left" verticalAlignment="Middle"/>
<textFieldExpression><![CDATA["First Name: " + $P{firstName}]]></textFieldExpression>
</textField>
<textField>
<reportElement x="0" y="71" width="480" height="30" uuid="28093443-193a-48da-868b-fae584890204"/>
<textElement textAlignment="Left" verticalAlignment="Middle"/>
<textFieldExpression><![CDATA["Last Name: " + $P{lastName}]]></textFieldExpression>
</textField>
<textField>
<reportElement x="1" y="101" width="479" height="30" uuid="ec94ccf6-8e5b-4a98-8f20-3d709d2249c5"/>
<textElement textAlignment="Left" verticalAlignment="Middle"/>
<textFieldExpression><![CDATA["Product: " + $P{productName}]]></textFieldExpression>
</textField>
<textField>
<reportElement x="0" y="161" width="480" height="30" uuid="59552cce-4169-4f71-a3e1-b7baef6fd549"/>
<textElement textAlignment="Left" verticalAlignment="Middle"/>
<textFieldExpression><![CDATA["Order Id: " + $P{orderId}]]></textFieldExpression>
</textField>
<textField>
<reportElement x="0" y="192" width="480" height="30" uuid="206025c4-75e5-4d2a-92c6-b3e34cd72d51"/>
<textElement textAlignment="Left" verticalAlignment="Middle"/>
<textFieldExpression><![CDATA["Order Date: " + $P{orderDate}]]></textFieldExpression>
</textField>
<textField>
<reportElement x="0" y="131" width="480" height="30" uuid="db5d78d6-c943-424c-8de6-8710fbe1e344"/>
<textElement textAlignment="Left" verticalAlignment="Middle"/>
<textFieldExpression><![CDATA["Product Price: " + $P{price}]]></textFieldExpression>
</textField>
</band>
</detail>
</jasperReport>
After designing the layout and adding the necessary parameters, we can go to the IDE and include these dependencies in our pom.xml file:
<dependency>
<groupId>net.sf.jasperreports</groupId>
<artifactId>jasperreports</artifactId>
<version>6.17.0</version>
</dependency>
<dependency>
<groupId>net.sf.jasperreports</groupId>
<artifactId>jasperreports-fonts</artifactId>
<version>6.17.0</version>
</dependency>
Jaspersoft Studio can do the compilation to .jasper on the fly, but the compilation can also get achieved at runtime using the JasperCompileManager
class. This code example shows how we pass dynamic data to the compiled template:
public class JasperReportsDemo {
private static final Logger logger = LoggerFactory.getLogger(JasperReportsDemo.class);
public static void main(String[] args) throws IOException {
JasperReportsDemo demo = new JasperReportsDemo();
Map<String, Object> orderInfo = new HashMap<>();
orderInfo.put("firstName", "Hamza");
orderInfo.put("lastName", "Belmellouki");
orderInfo.put("productName", "Effective Java (3rd Edition)");
orderInfo.put("price", "45 $");
orderInfo.put("orderId", "6f64b98a-a0eb-4e75-b436-030258872f43");
orderInfo.put("orderDate", LocalDate.of(2021, Month.AUGUST, 1).toString());
demo.generateDocument(orderInfo);
}
public void generateDocument(Map<String, Object> parameters) throws IOException {
File file = new File(getClass().getClassLoader()
.getResource("order-info.jrxml").getFile());
JasperReport jasperReport = null;
try (InputStream inputStream = new ByteArrayInputStream(Files.readAllBytes(file.toPath()))) {
jasperReport = JasperCompileManager.compileReport(inputStream);
} catch (JRException | IOException e) {
logger.error("pdf generation error ", e);
}
OutputStream outputStream = new FileOutputStream("order-info.pdf");
/* Write content to PDF file */
outputStream.write(getPdfContent(jasperReport, parameters));
}
public static byte[] getPdfContent(JasperReport jasperReport, Map<String, Object> parameters) {
byte[] content = new byte[0];
JasperPrint jasperPrint;
try {
/* Using compiled version(.jasper) of Jasper report to generate PDF */
jasperPrint = JasperFillManager
.fillReport(jasperReport, parameters, new JREmptyDataSource());
content = JasperExportManager.exportReportToPdf(jasperPrint);
} catch (JRException e) {
logger.error("pdf generation error ", e);
}
return content;
}
}
On line 25, we compile the JRXML to .jasper format. The getPdfContent
method returns the content as a byte array. Last, in line 32, we write the content to a PDF file. Running the main
method will generate a pdf populated with parameter’s data:

I prefer to compile the report programmatically as I don’t have to worry about the compatibility of the binary across environments. If you’re using Spring, you can compile the JRXML file at the app’s startup or after the bean is initialized in the @PostConstruct
method, so you don’t have to compile it each time you make a request.
Test the report content
Kent Beck once said: “Testing is not the point. The point is about responsibility”. So let’s test that the pdf generation is working as expected. This test loads the JRXML file, compile it, and generate the pdf content with the passed parameters as byte array:
@Test
public void getPdfContent() throws Exception {
File file = new File(getClass().getClassLoader()
.getResource("order-info.jrxml").getFile());
JasperReport jasperReport = null;
try (InputStream inputStream = new ByteArrayInputStream(Files.readAllBytes(file.toPath()))) {
jasperReport = JasperCompileManager.compileReport(inputStream);
} catch (JRException | IOException e) {
logger.error("pdf generation error ", e);
}
Map<String, Object> orderInfo = new HashMap<>();
orderInfo.put("firstName", "Hamza");
orderInfo.put("lastName", "Belmellouki");
orderInfo.put("productName", "Effective Java (3rd Edition)");
orderInfo.put("price", "45 $");
orderInfo.put("orderId", "6f64b98a-a0eb-4e75-b436-030258872f43");
orderInfo.put("orderDate", LocalDate.of(2021, Month.AUGUST, 1).toString());
byte[] content = JasperReportsDemo.getPdfContent(jasperReport, orderInfo);
assertNotNull(content);
PdfTextExtractor parser = new PdfTextExtractor(
new PdfReader(new ByteArrayInputStream(content)));
String pdfText = parser.getTextFromPage(1);
assertThat(pdfText, CoreMatchers.containsString("Hamza"));
assertThat(pdfText, CoreMatchers.containsString("Belmellouki"));
assertThat(pdfText, CoreMatchers.containsString("Effective Java (3rd Edition)"));
assertThat(pdfText, CoreMatchers.containsString("45 $"));
assertThat(pdfText, CoreMatchers.containsString("6f64b98a-a0eb-4e75-b436-030258872f43"));
assertThat(pdfText, CoreMatchers.containsString(LocalDate.of(2021, Month.AUGUST, 1).toString()));
}
In these assertions, we’re testing that the passed text parameters got into the pdf content. This test tests the correctness of the data in the generated pdf document.
Wrap Up
In this post, I attempted to clarify how jasper reports work and show you how to work with Jasper Studio and to pass dynamic data to a report template. We also saw how to test the correctness of the pdf content.
You can get the demo code in thisĀ Github repository. This is a Maven-based project, so it should be easy to import and run as-is.
If you happen to find these articles useful, you can buy me a coffee.