Modular Library for JasperReports: Simplifying Subreports and Data
Working with large reports in JasperReports often leads to monolithic JRXML files. Design, data logic, and business rules get mixed together, making maintenance difficult. Subreports address modularity, but passing data into them introduces complexities.
Typical data approaches:
- Single JSON: JsonDataSource with paths in XML. Path errors aren't caught at compile time, and REPORT_DATA_SOURCE is passed implicitly.
- SQL in templates: REPORT_CONNECTION for queries. Logic resides outside Java, with no type checks.
- Passing REPORT_DATA_SOURCE: Consumable data sources are exhausted after the first subreport.
- Manual parameters: Dozens of <subreportParameter> and params.put() in Java, requiring synchronization.
The jasper-modular principle
The library passes two parameters into each subreport:
<prefix>Report— the compiled JasperReport.<prefix>MapParameter— a Map<String, Object> containing data.
The map is unpacked via REPORT_PARAMETERS_MAP: keys become $P{key} without <subreportParameter>. An annotation processor generates parameters and JRXML during compilation. The renderer recursively populates subreports using reflection.
Adding dependencies
For JasperReports 7.x:
<dependency>
<groupId>io.github.hhdevr</groupId>
<artifactId>jasper-modular-starter-jr7</artifactId>
<version>1.0.0</version>
</dependency>
For 6.x:
<dependency>
<groupId>io.github.hhdevr</groupId>
<artifactId>jasper-modular-starter-jr6</artifactId>
<version>1.0.0</version>
</dependency>
Annotation processor in maven-compiler-plugin:
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<annotationProcessorPaths>
<path>
<groupId>io.github.hhdevr</groupId>
<artifactId>jasper-modular-processor-jr7</artifactId>
<version>1.0.0</version>
</path>
</annotationProcessorPaths>
</configuration>
</plugin>
Report class structure
Root report:
@Getter
@Setter
@JasperModularReport(templatePath = "/reports/company_report.jrxml")
public class CompanyReport extends ModularReport {
private TitleSubModule titleSubModule;
private FinancialSubModule financialSubModule;
}
Subreport:
@Getter
@Setter
@JasperSubreport(templatePath = "/reports/sub_title_report.jrxml", prefix = "Title")
public class TitleSubModule extends SubreportModule {
private CompanyDetails companyDetails;
private String period;
private String currency;
@Override
public boolean isEmpty() {
return companyDetails == null;
}
}
Nesting: FinancialSubModule contains RevenueSubModule, ExpenseSubModule, ProfitSubModule. Each module checks isEmpty() for conditional rendering.
Collections:
@JasperCollection(columnWidth = 150)
private List<RevenueItem> items;
Excluding fields:
@JasperIgnore
private transient String internalCache;
JRXML generation modes
The annotation processor updates templates in target/generated-sources/:
- CREATE: Generates a full JRXML with parameters, datasets, collection components, and subreport bands.
- INJECT (default): Adds missing elements while preserving existing design.
- NONE: No changes.
After generation, templates can be refined in Jaspersoft Studio.
Sample project tree:
- CompanyReport
- TitleSubModule (CompanyDetails, period, currency)
- FinancialSubModule
- RevenueSubModule (totalRevenue, growthPercent, List<RevenueItem>)
- ExpenseSubModule (totalExpenses, growthPercent, List<ExpenseItem>)
- ProfitSubModule (grossProfit, operatingProfit, netProfit, margin, List<ProfitBreakdown>)
Key takeaways
- Modularity via Java classes: subreports as fields, with typed data.
- Automatic parameter and JRXML generation by the annotation processor.
- Recursive rendering without manual data passing.
- Support for collections with @JasperCollection and unlimited nesting depth.
- Compatibility with JasperReports 6.x/7.x and Spring Boot 3.x/4.x.
— Editorial Team
No comments yet.