Spring Boot web JasperReports integration

In this tutorial, we show how to use JasperReports with Spring Boot framework. We create a web application.

JasperReports is a Java open source reporting library. It can create reports in various formats including PDF, HTML, XLS, or CSV. JasperReports creates page-oriented, ready-to-print documents in a simple and flexible manner.

JdbcTemplate is a Spring library that helps programmers create applications that work with relational databases and JDBC. It takes care of many tedious and error-prone low-level details such as handling transactions, cleaning up resources, and correctly handling exceptions. JdbcTemplate is shipped in Spring's spring-jdbc module.

Spring is a Java application framework for developing Java enterprise applications. It also helps integrate various enterprise components. Spring Boot makes it easy to create Spring-powered, production-grade applications and services with minimum setup requirements.

Apache Derby is an open source relational database implemented entirely in Java. It has small footprint and is easy to deploy and install. It can be run in embedded and client/server modes.

The CARS table

We use the following table:

cars.sql
-- SQL for the CARS table

CREATE TABLE CARS(ID BIGINT NOT NULL PRIMARY KEY GENERATED ALWAYS AS IDENTITY 
    (START WITH 1, INCREMENT BY 1), NAME VARCHAR(30), PRICE INT);
INSERT INTO CARS(Name, Price) VALUES('Audi', 52642);
INSERT INTO CARS(Name, Price) VALUES('Mercedes', 57127);
INSERT INTO CARS(Name, Price) VALUES('Skoda', 9000);
INSERT INTO CARS(Name, Price) VALUES('Volvo', 29000);
INSERT INTO CARS(Name, Price) VALUES('Bentley', 350000);
INSERT INTO CARS(Name, Price) VALUES('Citroen', 21000);
INSERT INTO CARS(Name, Price) VALUES('Hummer', 41400);
INSERT INTO CARS(Name, Price) VALUES('Volkswagen', 21600);

The cars.sql file creates the CARS table.

$ $DERBY_HOME/bin/ij
ij version 10.11
ij> CONNECT 'jdbc:derby:testdb';
ij> RUN 'cars.sql';

One option is to use the ij tool to create the table from the SQL script. Refer to Apache Derby tutorial to familiarize yourself with Derby.

$ $DERBY_HOME/bin/NetworkServerControl start &

Derby server is started with NetworkServerControl tool.

Application

The following Spring Boot application loads data from a database table and produces a PDF report from it with JasperReports library. The application runs with embedded Tomcat server.

$ tree
.
├── pom.xml
└── src
    ├── main
    │   ├── java
    │   │   └── com
    │   │       └── zetcode
    │   │           ├── bean
    │   │           │   └── Car.java
    │   │           ├── conf
    │   │           │   └── AppConfig.java
    │   │           ├── main
    │   │           │   └── Application.java
    │   │           ├── service
    │   │           │   └── CarService.java
    │   │           └── web
    │   │               └── MyController.java
    │   ├── resources
    │   │   ├── application.yml
    │   │   └── report2.jrxml
    │   └── webapp
    │       └── WEB-INF
    │           └── views
    │               └── index.jsp
    └── test
        └── java

This is the project structure.

pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" 
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 
                             http://maven.apache.org/xsd/maven-4.0.0.xsd">
    
    <modelVersion>4.0.0</modelVersion>
    <groupId>com.zetcode</groupId>
    <artifactId>JasperSpringBootWeb</artifactId>
    <version>1.0-SNAPSHOT</version>
    <packaging>war</packaging>
    
    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <maven.compiler.source>1.8</maven.compiler.source>
        <maven.compiler.target>1.8</maven.compiler.target>
    </properties>
    
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>1.5.2.RELEASE</version>
    </parent>    
    
    <dependencies>
        <dependency>
            <groupId>org.apache.derby</groupId>
            <artifactId>derbyclient</artifactId>
            <version>10.13.1.1</version>
        </dependency>  
        
        <dependency>
            <groupId>net.sf.jasperreports</groupId>
            <artifactId>jasperreports</artifactId>
            <version>6.4.0</version>
        </dependency>   
        
        <dependency>
            <groupId>org.apache.tomcat.embed</groupId>
            <artifactId>tomcat-embed-jasper</artifactId>
            <version>8.5.12</version>
        </dependency>
        
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-webmvc</artifactId>
        </dependency>      
        
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context-support</artifactId>
        </dependency>   
        
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>                                      
        
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-jdbc</artifactId>
        </dependency>        
       
    </dependencies>    

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>            
        </plugins>
    </build>    
    
</project>

The Maven pom.xml file contains dependencies for JasperReports library, Derby driver, and Spring framework.

report2.xml
<?xml version = "1.0" encoding = "UTF-8"?>
<!DOCTYPE jasperReport PUBLIC "//JasperReports//DTD Report Design//EN"
   "http://jasperreports.sourceforge.net/dtds/jasperreport.dtd">

<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="report2" pageWidth="595" pageHeight="842" 
              columnWidth="555" leftMargin="20" rightMargin="20"
              topMargin="20" bottomMargin="20">
  
    <field name="Id" class="java.lang.Long">
        <fieldDescription><![CDATA[id]]></fieldDescription>
    </field>    
    
    <field name="Name" class="java.lang.String">
        <fieldDescription><![CDATA[name]]></fieldDescription>
    </field>
	
    <field name="Price" class="java.lang.Integer">
        <fieldDescription><![CDATA[price]]></fieldDescription>
    </field>
   
    <detail>
        <band height="15">
          
            <textField>
                <reportElement x="0" y="0" width="50" height="15" />
            
                <textElement textAlignment="Right" verticalAlignment="Middle"/>
            
                <textFieldExpression class="java.lang.Long">
                    <![CDATA[$F{Id}]]>
                </textFieldExpression>
            </textField>       
         
            <textField>
                <reportElement x="150" y="0" width="100" height="15" />
            
                <textElement textAlignment="Left" verticalAlignment="Middle"/>
            
                <textFieldExpression class="java.lang.String">
                    <![CDATA[$F{Name}]]>
                </textFieldExpression>
            </textField>               
          
            <textField>
                <reportElement x="200" y="0" width="100" height="15"/>
                <textElement textAlignment="Right" verticalAlignment="Middle"/>
            
                <textFieldExpression class="java.lang.Integer">
                    <![CDATA[$F{Price}]]>
                </textFieldExpression>
            </textField>          

        </band>
    </detail>

</jasperReport>

This is the report template file. The template contains only the detail band. Inside a detail band, each element is repeated for every record provided by the data source.

<field name="Id" class="java.lang.Long">
    <fieldDescription><![CDATA[id]]></fieldDescription>
</field>    

<field name="Name" class="java.lang.String">
    <fieldDescription><![CDATA[name]]></fieldDescription>
</field>
    
<field name="Price" class="java.lang.Integer">
    <fieldDescription><![CDATA[price]]></fieldDescription>
</field>

We have three fields in the report. The fields are mapped to the elements of the data source beans.

<textField>
    <reportElement x="0" y="0" width="50" height="15" />

    <textElement textAlignment="Right" verticalAlignment="Middle"/>

    <textFieldExpression class="java.lang.Long">
        <![CDATA[$F{Id}]]>
    </textFieldExpression>
</textField>   

A text field is an element that is filled with dynamic data. We place a value from a field inside the text field. We refer to the variable with the $F{} syntax.

Car.java
package com.zetcode.bean;

public class Car {
    
    private Long id;
    private String name;
    private int price;
    
    public Car() {}

    public Car(Long id, String name, int price) {
        this.id = id;
        this.name = name;
        this.price = price;
    }

    public Long getId() {
        return id;
    }

    public void setId(Long id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getPrice() {
        return price;
    }

    public void setPrice(int price) {
        this.price = price;
    }

    @Override
    public String toString() {
        return "Car{" + "id=" + id + ", name=" + name + ", price=" + price + '}';
    }
}

This is Car bean class. It contains item ID, name, and price.

application.yml
datasource:
  url: jdbc:derby://localhost:1527/testdb
  username: app
  password: app
  driverClassName: org.apache.derby.jdbc.ClientDriver

spring: 
       mvc:
           view:
                prefix: /WEB-INF/views/
                suffix: .jsp

The application.yml is the main Spring Boot configuration file. It contains the Derby datasource and Spring MVC settings.

AppConfig.java
package com.zetcode.conf;

import javax.sql.DataSource;
import org.springframework.boot.autoconfigure.jdbc.DataSourceBuilder;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;

@Configuration
public class AppConfig {

    @Bean
    @Primary
    @ConfigurationProperties(prefix = "datasource")
    public DataSource primaryDataSource() {
        return DataSourceBuilder.create().build();
    }
}

The AppConfig is a Java configuration class. It creates the data source bean from the configuration file.

MyController.java
package com.zetcode.web;

import com.zetcode.service.ICarService;
import java.util.HashMap;
import java.util.Map;
import java.util.logging.Level;
import java.util.logging.Logger;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.view.jasperreports.JasperReportsPdfView;

@Controller
public class MyController {

    @Autowired
    private ApplicationContext appContext;

    @Autowired
    private ICarService carService;

    @RequestMapping("/")
    public String index(Model model) {

        return "index";
    }

    @RequestMapping(name = "/pdf", method = RequestMethod.GET)
    public ModelAndView report() {

        JasperReportsPdfView view = new JasperReportsPdfView();
        view.setUrl("classpath:report2.jrxml");
        view.setApplicationContext(appContext);

        Map<String, Object> params = new HashMap<>();
        params.put("datasource", carService.findAll());

        return new ModelAndView(view, params);
    }
}

In the MyController, we have two methods that react to two requests.

@Autowired
private ICarService carService;

We inject CarService object into the attribute. The service object is used to retrieve data from the database.

@RequestMapping("/")
public String index(Model model) {

    return "index";
}

For the root path, we return index string, which is mapped to index.jsp inside WEB-INF/views.

@RequestMapping(name = "/pdf", method = RequestMethod.GET)
public ModelAndView report() {

    JasperReportsPdfView view = new JasperReportsPdfView();
    view.setUrl("classpath:report2.jrxml");
    view.setApplicationContext(appContext);

    Map<String, Object> params = new HashMap<>();
    params.put("datasource", carService.getCars());

    return new ModelAndView(view, params);
}

In the report() method, a report is generated and sent back to the client. The JasperReportsPdfView is a Spring class that generates a PDF report from the provided template and data.

ICarService.java
package com.zetcode.service;

import com.zetcode.bean.Car;
import java.util.List;

public interface ICarService {

    public List<Car> findAll();
}

ICarService provides a contract method to get all cars from the data source.

CarService.java
package com.zetcode.service;

import com.zetcode.bean.Car;
import java.util.List;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.BeanPropertyRowMapper;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Service;

@Service
public class CarService implements ICarService {

    @Autowired
    private JdbcTemplate jtm;

    @Override
    public List<Car> findAll() {

        String sql = "SELECT * FROM Cars";

        List<Car> cars = jtm.query(sql, new BeanPropertyRowMapper(Car.class));

        return cars;
    }
}

CarService contains the implementation of the findAll() method. We retrieve all cars from the CARS table with the help of the JdbcTemplate.

@Autowired
private JdbcTemplate jtm;

JdbcTemplate is injected.

String sql = "SELECT * FROM Cars";

This is SQL to be executed. We select all cars from the CARS table.

List<Car> cars = jtm.query(sql, new BeanPropertyRowMapper(Car.class));

BeanPropertyRowMapper converts a row into a new instance of the specified mapped target class.

index.jsp
<%@page contentType="text/html" pageEncoding="UTF-8"%>
<!DOCTYPE html>
<html>
    <head>
        <meta charset="UTF-8">
        <title>Home Page</title>
    </head>
    <body>
        <a href="/pdf.html">Generate report</a>
    </body>
</html>

The index.jsp file contains a link to generate PDF report.

Application.java
package com.zetcode.client;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.context.annotation.ComponentScan;

@EnableAutoConfiguration
@ComponentScan(basePackages="com.zetcode")
public class Application {

    public static void main(String[] args) {

        SpringApplication.run(Application.class, args);
    }
}

The Application sets up the Spring Boot application.

In this tutorial, we have created a PDF report with JasperReports. The application used Spring Boot framework and was run in a web environment. You might also be interested in these related tutorials: Spring Boot JasperReports cmd integration, Creating a report with JasperReports API, Creating a report from CSV with JasperReports, and Java tutorial.