Java generates large PDFs with JasperReports.

JasperReports is an open-source Java reporting tool that enables users to create, design, and generate reports in various formats, including PDF, HTML, Excel, CSV, and XML. It is widely used for generating dynamic content from multiple data sources, such as alternative Crystal Reports databases, XML files, and custom data sources.

Problem

In this scenario, the developer generates monthly or yearly PDF reports. A report contains a lot of data that consumes a lot of memory.

In the other scenario, an application handles many clients, generating real-time reports simultaneously that consume logical memory.

A standard error that developers get from generating large PDFs.

Exception in thread "main" java.lang.OutOfMemoryError: Java heap space

Download JasperSoft Studio

The developer uses JasperSoft Studio to create PDFs with Java.

Example: generate a PDF report with JasperReports.

1. In pom.xml, add a dependency.

  <!-- https://mvnrepository.com/artifact/net.sf.jasperreports/jasperreports -->
  <dependency>
   <groupId>net.sf.jasperreports</groupId>
   <artifactId>jasperreports</artifactId>
   <version>7.0.0</version>
  </dependency>
  <!-- https://mvnrepository.com/artifact/net.sf.jasperreports/jasperreports-fonts -->
  <dependency>
   <groupId>net.sf.jasperreports</groupId>
   <artifactId>jasperreports-fonts</artifactId>
   <version>7.0.0</version>
  </dependency>
  <!-- https://mvnrepository.com/artifact/net.sf.jasperreports/jasperreports-pdf -->
  <dependency>
   <groupId>net.sf.jasperreports</groupId>
   <artifactId>jasperreports-pdf</artifactId>
   <version>7.0.0</version>
  </dependency>

2. Create a JRXML file with Jaspersoft Studio.
The developer can download Jaspersoft Studio.

Jaspersoft Studio home page

3. Create JRXML to generate a monthly PDF report on user activity data.

PDF template

4. Create parameters and fields.

Outline

5. The developer can add more elements to learn and practice JasperReports.

Palette

6. In the design tab, the developer can design a template for a report.
7. In the Source tab, the developer can view the source code of a report.
8. In the preview tab, the developer can preview a report.

Jaspersoft Studio JRXML

9. Create a UserActivity bean for the report detail band.

import lombok.Getter;
import lombok.Setter;

@Getter
@Setter
public class UserActivity {
    private String userId;
    private String activityType;
    private String activityTime;
    private String ipAddress;
    private String deviceType;
    private String location;
    private String browserType;
    private String operatingSystem;
}

10. Create the GeneratePDFJasperReport class to generate a PDF report.

package com.example.demo.sandbox;

import com.example.demo.bean.UserActivity;
import net.sf.jasperreports.engine.JasperReport;
import net.sf.jasperreports.engine.JasperCompileManager;
import net.sf.jasperreports.engine.JRDataSource;
import net.sf.jasperreports.engine.JasperPrint;
import net.sf.jasperreports.engine.JasperFillManager;
import net.sf.jasperreports.engine.JasperExportManager;
import net.sf.jasperreports.engine.JRException;
import net.sf.jasperreports.engine.data.JRBeanCollectionDataSource;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

public class GeneratePDFJasperReport {

    public static void main(String[] args) {
        generateReport();
    }

    public static void generateReport(){
        try {
            // Compile the Jasper report from .jrxml to .jasper
            String sourceFileName = "C:/JaspersoftWorkspace/MyReports/demo_large.jrxml";
            JasperReport jasperReport = JasperCompileManager.compileReport(sourceFileName);

            // Generate sample data
            List<UserActivity> activities = generateUserActivities();

            // Create a JRDataSource from the list
            JRDataSource jrDataSource = new JRBeanCollectionDataSource(activities);

            // Parameters for the report
            Map<String, Object> parameters = new HashMap<>();
            parameters.put("P_MONTH","JULY");
            parameters.put("P_YEAR","2024");

            // Fill the report
            JasperPrint jasperPrint = JasperFillManager.fillReport(jasperReport, parameters, jrDataSource);

            // Export the report to a PDF file
            String outputFileName = "D:/pdf/report.pdf";
            JasperExportManager.exportReportToPdfFile(jasperPrint, outputFileName);

            System.out.println("Report generated successfully!");
        } catch (JRException e) {
            e.printStackTrace();
        }
    }

    public static List<UserActivity> generateUserActivities() {
        List<UserActivity> activities = new ArrayList<>();

        for (int i = 1; i <= 10; i++) {
            UserActivity activity = new UserActivity();
            activity.setUserId(String.valueOf(i));
            activity.setActivityType("activity");
            activity.setActivityTime("2024-07-07 10:00");
            activity.setIpAddress("192.168.0.1");
            activity.setDeviceType("PC");
            activity.setLocation("US");
            activity.setBrowserType("CHROME");
            activity.setOperatingSystem("WINS");

            activities.add(activity);
        }

        return activities;
    }

}

Test generate PDF with Intelij.

1. Execute GeneratePDFJasperReport main to generate PDF.

Report generated successfully!
PDF

2. In this example, JasperReports uses only logical memory.

3. When the data in user activity has hundreds of thousands or millions of records, it causes the “OutOfMemoryError”.

Monitor the memory usage of Java applications.

To monitor logical memory, the developer can use IntelliJ IDEA Ultimate for profiling.

The other option is free, VisualVM, a visual tool that integrates command-line JDK tools and lightweight profiling capabilities.

Generate a large PDF.

1. To simulate the ” OutOfMemoryError ” case by reducing memory,
Add VM options -Xmx256m -Xms128m and increase user activity data to 200K records.

VM options
public static List<UserActivity> generateUserActivities() {
    List<UserActivity> activities = new ArrayList<>();

    for (int i = 1; i <= 200000; i++) {
        UserActivity activity = new UserActivity();
        activity.setUserId(String.valueOf(i));
        activity.setActivityType("activity");
        activity.setActivityTime("2024-07-07 10:00:00");
        activity.setIpAddress("192.168.0.1");
        activity.setDeviceType("PC");
        activity.setLocation("US");
        activity.setBrowserType("CHROME");
        activity.setOperatingSystem("WINS");

        activities.add(activity);
    }

    return activities;
}

2. Open VisualVM to monitor memory usage.
3. When executing the “main” method. It causes Java heap space errors.

Exception in thread "main" java.lang.OutOfMemoryError: Java heap space
Exception in thread "RMI TCP Connection(idle)" java.lang.OutOfMemoryError: Java heap space

4. VisualVM shows memory usage.

VisualVM monitor

5. Memory usage peaks when generating PDF and causes Java heap space errors.

Virtualizer

JasperReports provides a feature to solve this problemVirtualizer controls memory usage by using physical memory to help generate large PDFs.

JRSwapFileVirtualizer

manages memory by offloading data from memory to disk.

JRFileVirtualizer

Writes data to multiple temporary files rather than a single swap file.

JRGzipVirtualizer

This virtualizer compresses data before writing it to disk, saving disk space at the cost of additional CPU usage for compression and decompression.

Generate a large PDF with Virtualizer.

JRSwapFileVirtualizer

public static void generateReportVirtualizer(){
    try {
        // Compile the Jasper report from .jrxml to .jasper
        String sourceFileName = "C:/JaspersoftWorkspace/MyReports/demo_large.jrxml";
        JasperReport jasperReport = JasperCompileManager.compileReport(sourceFileName);

        // Generate sample data
        List<UserActivity> activities = generateUserActivities();

        // Create a JRSwapFileVirtualizer
        JRSwapFile jrSwapFile = new JRSwapFile("C:/JaspersoftWorkspace/MyReports/",1024,1);
        JRSwapFileVirtualizer virtualizer = new JRSwapFileVirtualizer(20, jrSwapFile, true);

        // Create a JRDataSource from the list
        JRDataSource jrDataSource = new JRBeanCollectionDataSource(activities);

        // Parameters for the report
        Map<String, Object> parameters = new HashMap<>();
        parameters.put("P_MONTH","JUNE");
        parameters.put("P_YEAR","2024");
        parameters.put(JRParameter.REPORT_VIRTUALIZER, virtualizer);

        // Fill the report
        JasperPrint jasperPrint = JasperFillManager.fillReport(jasperReport, parameters, jrDataSource);

        // Export the report to a PDF file
        String outputFileName = "D:/pdf/report.pdf";
        JasperExportManager.exportReportToPdfFile(jasperPrint, outputFileName);

        System.out.println("Report generated successfully!");
    } catch (JRException e) {
        e.printStackTrace();
    }
}

1. Run the “main” method and use VisualVM to monitor memory usage.

VisualVM monitor
Report generated successfully!
PDF page 1/2
PDF page 2/2

2. The report was generated successfully without a memory error.
On the VisualVM, memory usage does not reach peak memory usage.

3. The developer should manage the disk space because JRSwapFileVirtualizer uses physical memory. After generating a report, the developer must delete the temporary file created by JRSwapFileVirtualizer.

JRGzipVirtualizer

public static void generateReportVirtualizer(){
    try {
        // Compile the Jasper report from .jrxml to .jasper
        String sourceFileName = "C:/JaspersoftWorkspace/MyReports/demo_large.jrxml";
        JasperReport jasperReport = JasperCompileManager.compileReport(sourceFileName);

        // Generate sample data
        List<UserActivity> activities = generateUserActivities();

        // Create a JRGzipVirtualizer
        JRGzipVirtualizer virtualizer = new JRGzipVirtualizer(50);

        // Create a JRDataSource from the list
        JRDataSource jrDataSource = new JRBeanCollectionDataSource(activities);

        // Parameters for the report
        Map<String, Object> parameters = new HashMap<>();
        parameters.put("P_MONTH","JUNE");
        parameters.put("P_YEAR","2024");
        parameters.put(JRParameter.REPORT_VIRTUALIZER, virtualizer);

        // Fill the report
        JasperPrint jasperPrint = JasperFillManager.fillReport(jasperReport, parameters, jrDataSource);

        // Export the report to a PDF file
        String outputFileName = "D:/pdf/report.pdf";
        JasperExportManager.exportReportToPdfFile(jasperPrint, outputFileName);

        System.out.println("Report generated successfully!");
    } catch (JRException e) {
        e.printStackTrace();
    }
}

1. Run the “main” method and use VisualVM to monitor memory usage.

VisualVM monitor

2. The report was generated successfully without a memory error.
On VisualVM, memory usage does not reach peak. However, JRSwapFileVirtualizer has a different memory usage pattern.

JRFileVirtualizer

public static void generateReportVirtualizer(){
        try {
            // Compile the Jasper report from .jrxml to .jasper
            String sourceFileName = "C:/JaspersoftWorkspace/MyReports/demo_large.jrxml";
            JasperReport jasperReport = JasperCompileManager.compileReport(sourceFileName);

            // Generate sample data
            List<UserActivity> activities = generateUserActivities();

            // Create a JRFileVirtualizer
            int maxSize = 100; // Max number of pages in memory
            String tempDir = "C:/JaspersoftWorkspace/MyReports/"; // Temp directory for virtualizer files
            JRFileVirtualizer virtualizer = new JRFileVirtualizer(maxSize, tempDir);


            // Create a JRDataSource from the list
            JRDataSource jrDataSource = new JRBeanCollectionDataSource(activities);

            // Parameters for the report
            Map<String, Object> parameters = new HashMap<>();
            parameters.put("P_MONTH","JUNE");
            parameters.put("P_YEAR","2024");
            parameters.put(JRParameter.REPORT_VIRTUALIZER, virtualizer);

            // Fill the report
            JasperPrint jasperPrint = JasperFillManager.fillReport(jasperReport, parameters, jrDataSource);

            // Export the report to a PDF file
            String outputFileName = "D:/pdf/report.pdf";
            JasperExportManager.exportReportToPdfFile(jasperPrint, outputFileName);

            System.out.println("Report generated successfully!");
        } catch (JRException e) {
            e.printStackTrace();
        }
    }

1. Run the “main” method and use VisualVM to monitor memory usage.

VisualVM monitor

2. The report was generated successfully without a memory error.

temporary file

3. A temporary file is created when generating a PDF with JRFileVirtualizer. When the PDF is finished being generated, the temporary files are deleted.

Precompilation

Jaspersoft Studio provides the feature to compile “.jrxml” to “.jasper”.
Compiling reports can detect some errors. The developer can compile a report to check for mistakes before implementing JasperReports in the Java application.

jrxml

The developer can use “.jasper” instead of “.jrxml” to save resources.

String jasperFile = "C:/JaspersoftWorkspace/MyReports/demo_large.jasper";
JasperReport jasperReport = (JasperReport) JRLoader.loadObjectFromFile(jasperFile);

Finally

Each Virtualizer has pros and cons. The developer must decide which virtualizer to use when generating PDFs, based on the condition of each individual report.

Leave a Comment

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