Jasper Report 101
Jasper Report Quick Guide
Jasper Report 101
What Is JasperReports?
JasperReports is an open-source Java library for generating dynamic, pixel-perfect reports — invoices, financial statements, shipping labels, dashboards, anything that needs to look the same whether it's on screen or on paper. You design a report as an XML template, feed it data at runtime, and it handles pagination and layout for you, then exports the result to PDF, HTML, XLS/XLSX, CSV, RTF, ODT, DOCX, or plain XML.
It's been a default reporting choice in the Java ecosystem for over two decades, especially in Spring-based backends, because it can pull data from almost anywhere — JDBC, JPA/Hibernate entities, plain Java beans, CSV, JSON, even XML — without needing a separate reporting server running alongside your app.
A Brief History
JasperReports has changed hands more times than most people realize, but the core library has stayed open-source through all of it.
- 2001 — Romanian software engineer Teodor Danciu started the project as a personal effort to build an affordable, embeddable Java reporting engine. The SourceForge project was registered that September, and the first release (0.1.5) shipped in November.
- 2004–2005 — Danciu partnered with the founders of Panscopic, who acquired the JasperReports intellectual property and renamed the company Jaspersoft. JasperReports 1.0 shipped in July 2005, and the license moved to the LGPL, which the library still uses today.
- 2014 — TIBCO Software acquired Jaspersoft for around $185 million. Around the same time, the original standalone designer, iReport, was retired in favor of Jaspersoft Studio, an Eclipse-based plugin that's still the standard free design tool.
- 2022 — TIBCO became part of Cloud Software Group (formed when Vista Equity Partners merged TIBCO with Citrix), with Jaspersoft continuing on as one of its business units.
- 2024 — Cloud Software Group discontinued the free, self-hosted JasperReports Server Community Edition, narrowing the open-source footprint down to the core library and Jaspersoft Studio.
- 2025–2026 — HCLSoftware announced its intent to acquire Jaspersoft from Cloud Software Group in December 2025, folding it into its Actian data & AI division; the deal reportedly closed around $240 million and was completed on July 1, 2026.
Through every acquisition, the JasperReports Library — the part you actually embed in your app — has stayed open-source under the LGPL. As of this writing, the current stable release is 7.0.6 (March 2026), and Jaspersoft Studio is still free.
How It All Fits Together
Every report goes through the same pipeline, regardless of how you trigger it:
report.jrxml → compile → report.jasper → fill (with data) → JasperPrint → export → PDF / HTML / XLSX / ...
.jrxml— the human-readable XML source you design or hand-write..jasper— a compiled, serialized binary of that template. This is what actually runs when a report is generated, since re-parsing raw XML on every request would be slow.JasperPrint— an in-memory, already-paginated version of the filled report, independent of any output format.- Export — the same
JasperPrintcan be exported to PDF, Excel, HTML, etc. without re-running the fill step.
Keeping these stages separate is what lets you compile once and export the same report to five different formats without touching your data-fetching code.
Anatomy of a JRXML File
Every .jrxml is an XML document rooted at <jasperReport>, which declares the page size, margins, and — via the name attribute — what the compiled file will be called.
<?xml version="1.0" encoding="UTF-8"?>
<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="ProductSalesReport"
pageWidth="595" pageHeight="842"
columnWidth="555"
leftMargin="20" rightMargin="20"
topMargin="20" bottomMargin="20">
<parameter name="P_TITLE" class="java.lang.String"/>
<field name="name" class="java.lang.String"/>
<field name="price" class="java.lang.Double"/>
<variable name="V_TOTAL" class="java.lang.Double" calculation="Sum">
<variableExpression><![CDATA[$F{price}]]></variableExpression>
</variable>
<title>
<band height="40">
<textField>
<reportElement x="0" y="0" width="555" height="30"/>
<textFieldExpression><![CDATA[$P{P_TITLE}]]></textFieldExpression>
</textField>
</band>
</title>
<detail>
<band height="20">
<textField>
<reportElement x="0" y="0" width="300" height="20"/>
<textFieldExpression><![CDATA[$F{name}]]></textFieldExpression>
</textField>
<textField pattern="#,##0.00">
<reportElement x="300" y="0" width="255" height="20"/>
<textFieldExpression><![CDATA[$F{price}]]></textFieldExpression>
</textField>
</band>
</detail>
</jasperReport>
A few things worth knowing right away:
pageWidth/pageHeight/margins are measured in pixels at 72px per inch —595 × 842is a standard A4 page.<parameter>,<field>, and<variable>are just declarations; theclassattribute tells JasperReports what Java type to expect.- Every expression —
textFieldExpression,variableExpression, and so on — is wrapped in<![CDATA[...]]>so the XML parser doesn't trip over<,>, or&&inside your expression. <reportElement>positions everything withx/y/width/height, measured from the top-left corner of the band it's in — not the page.
Compiling Your Report
A .jrxml can't be filled with data directly — it has to be compiled into a .jasper file first. Three common ways to do that:
Jaspersoft Studio. The free Eclipse-based designer compiles automatically every time you save or preview, so you rarely touch the compile step by hand while designing.
Programmatically, at runtime:
import net.sf.jasperreports.engine.*;
import net.sf.jasperreports.engine.data.JRBeanCollectionDataSource;
import java.util.HashMap;
import java.util.Map;
public class ReportRunner {
public static void main(String[] args) throws JRException {
// 1. Compile .jrxml -> .jasper
JasperReport jasperReport =
JasperCompileManager.compileReport("reports/ProductSalesReport.jrxml");
// 2. Parameters + data source
Map<String, Object> parameters = new HashMap<>();
parameters.put("P_TITLE", "Monthly Sales Report");
JRBeanCollectionDataSource dataSource =
new JRBeanCollectionDataSource(productService.findAll());
// 3. Fill
JasperPrint jasperPrint =
JasperFillManager.fillReport(jasperReport, parameters, dataSource);
// 4. Export
JasperExportManager.exportReportToPdfFile(jasperPrint, "output/report.pdf");
}
}
Handy in development, but compiling on every request is wasteful in production — the report definition changes far less often than your data does.
Ahead-of-time, via the Maven plugin:
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>jasperreports-maven-plugin</artifactId>
<!-- check Maven Central for the current version -->
<executions>
<execution>
<phase>compile</phase>
<goals>
<goal>compile-reports</goal>
</goals>
</execution>
</executions>
<configuration>
<sourceDirectory>src/main/resources/reports</sourceDirectory>
<outputDirectory>${project.build.outputDirectory}/reports</outputDirectory>
</configuration>
</plugin>
This compiles every .jrxml to .jasper at build time, so your app only ever loads pre-compiled reports at runtime — the recommended setup for a production Spring Boot service. You'll also need the core library itself:
<dependency>
<groupId>net.sf.jasperreports</groupId>
<artifactId>jasperreports</artifactId>
<version>7.0.6</version>
</dependency>
There's also an older Ant task (JRAntCompileTask) that still shows up in legacy projects — same idea, different build tool.
Report Layout: Bands
A report is sliced horizontally into bands, each tied to a specific point in the print flow. JasperReports fires them in this order:
| Band | Fires |
|---|---|
background | Behind every page, drawn first |
title | Once, at the very start of the report |
pageHeader | Top of every page |
columnHeader | Top of every column (relevant for multi-column layouts) |
detail | Once per record in your data source — the repeating body |
columnFooter | Bottom of every column |
pageFooter | Bottom of every page |
lastPageFooter | Bottom of the last page only, replacing pageFooter there |
summary | Once, at the very end of the report |
Every one of these wraps a <band height="...">, and everything visible — text, images, lines — lives inside a band:
<pageFooter>
<band height="20">
<textField>
<reportElement x="500" y="0" width="55" height="20"/>
<textFieldExpression><![CDATA["Page " + $V{PAGE_NUMBER}]]></textFieldExpression>
</textField>
</band>
</pageFooter>
Key Tags: <frame> and Friends
A handful of tags come up in almost every report:
| Tag | Purpose |
|---|---|
<band> | A print-flow section container (see above) |
<frame> | A grouping container placed inside a band |
<staticText> | Fixed text that never changes |
<textField> | Text driven by an expression |
<image> | An image, from a file, URL, or byte-stream expression |
<line> / <rectangle> / <ellipse> | Basic shapes |
<subreport> | Embeds another compiled report (see below) |
<group> | Groups detail rows by an expression, like SQL's GROUP BY |
The distinction people usually trip on is band vs. frame. A band is a horizontal slice of the report tied to the print flow — you can't nest one band inside another. A frame is just a container you drop inside a band to cluster a group of elements so they move, stretch, and get a shared border together:
<detail>
<band height="80">
<frame>
<reportElement x="0" y="0" width="555" height="80"/>
<box>
<pen lineWidth="1.0" lineColor="#CCCCCC"/>
</box>
<textField>
<reportElement x="10" y="10" width="300" height="20"/>
<textFieldExpression><![CDATA[$F{name}]]></textFieldExpression>
</textField>
<textField pattern="#,##0.00">
<reportElement x="10" y="35" width="300" height="20"/>
<textFieldExpression><![CDATA[$F{price}]]></textFieldExpression>
</textField>
</frame>
</band>
</detail>
The <box><pen .../></box> here draws a light gray border around the whole frame — a common way to get "card-style" detail rows.
The Variable Symbols: $F, $P, $V, and $R
Every expression in a JRXML file draws from four kinds of symbols:
| Symbol | Name | Comes From | Example |
|---|---|---|---|
$F{name} | Field | One record from your data source | $F{price} |
$P{name} | Parameter | Your Java code, passed in at fill time | $P{P_TITLE} |
$V{name} | Variable | Calculated by JasperReports as it processes records | $V{V_TOTAL} |
$R{key} | Resource | A localized string from a resource bundle | $R{report.title} |
$F — Field. Declared with <field name="..." class="...">. One field maps to one property of each record — a column from a SQL result set, or a getter on a Java bean.
$P — Parameter. Declared with <parameter>. Parameters are set once per fill, from outside the report — a title, a date range, a logged-in user's name, even objects like a JDBC Connection. JasperReports also injects a handful of built-in parameters automatically, including REPORT_LOCALE, REPORT_RESOURCE_BUNDLE, REPORT_CONNECTION, and REPORT_DATA_SOURCE.
$V — Variable. Declared with <variable>. A variable holds a value JasperReports computes as it walks through your records:
<variable name="V_TOTAL" class="java.lang.Double" calculation="Sum">
<variableExpression><![CDATA[$F{price}]]></variableExpression>
</variable>
The calculation attribute picks the aggregation — Sum, Count, Average, Highest, Lowest, StandardDeviation, Variance, DistinctCount, First, or Nothing for fully manual control. A resetType attribute (Report, Page, Column, or Group) decides when the accumulator resets; Report, the default, never resets — which is why V_TOTAL above ends up holding the grand total.
$R — Resource. The one people forget exists. Attach a Java ResourceBundle to the report (via the resourceBundle attribute on <jasperReport>, or the REPORT_RESOURCE_BUNDLE parameter), and $R{key} pulls a locale-specific string instead of a hardcoded one:
<staticText>
<reportElement x="0" y="0" width="200" height="20"/>
<text><![CDATA[$R{report.title}]]></text>
</staticText>
With messages_en.properties containing report.title=Sales Report and messages_id.properties containing report.title=Laporan Penjualan, the exact same JRXML renders in either language, depending on which REPORT_LOCALE you pass in at fill time.
Two advanced variants exist too, worth knowing about but not essential on day one: $P!{name}, which inserts a parameter literally (unescaped) into a <queryString> — handy for dynamic table or column names in SQL — and $X{}, a function syntax for building conditional query fragments.
Sub-Reports
A subreport is just another compiled report embedded inside a parent one — the standard way to build master–detail layouts, like an order with its line items, without cramming everything into a single flat query.
<detail>
<band height="200">
<subreport>
<reportElement x="0" y="0" width="555" height="200"/>
<subreportParameter name="P_ORDER_ID">
<subreportParameterExpression><![CDATA[$F{orderId}]]></subreportParameterExpression>
</subreportParameter>
<dataSourceExpression>
<![CDATA[new net.sf.jasperreports.engine.data.JRBeanCollectionDataSource($F{orderItems})]]>
</dataSourceExpression>
<subreportExpression class="java.lang.String"><![CDATA["reports/OrderItems.jasper"]]></subreportExpression>
</subreport>
</band>
</detail>
The pieces:
<subreportParameter>— passes a value from the parent report into the subreport, where it just shows up as a normal$P{}.<dataSourceExpression>— hands the subreport its own data, here a Java collection wrapped as aJRBeanCollectionDataSource. Use<connectionExpression>instead if the subreport should run its own SQL query against a JDBC connection.<subreportExpression>— points to the compiled.jasperfile: aStringpath, anInputStream, or aJasperReportobject. Point it at a.jrxmlinstead and JasperReports will compile it on the fly, which is convenient for prototyping but wasteful in production.
A Few Beginner Gotchas
- Forgetting
<![CDATA[...]]>around an expression containing<,>, or&&— the XML parser throws an error that has nothing obviously to do with your actual logic. - Editing the
.jrxmlbut not recompiling. Jaspersoft Studio's preview does this automatically; a manualJasperCompileManagerpipeline won't, so you'll keep staring at stale output. - Passing
nullinstead of an empty data source. An emptyList(orJREmptyDataSource) triggers the<noData>band if you've defined one; anulldata source just throws. - Text getting clipped. A
<textField>with more content than fits its declaredheightneedstextAdjust="StretchHeight"to grow instead of truncate. - Garbled characters in exported PDFs. The default fonts only cover a limited character set — anything outside it needs a font extension JAR with the actual
.ttfembedded, registered through afonts.xmldescriptor.
Quick Reference
| Category | Symbol / Tag | Meaning |
|---|---|---|
| Variable | $F{name} | Field from the data source |
| Variable | $P{name} | Parameter passed in at fill time |
| Variable | $V{name} | Calculated variable |
| Variable | $R{key} | Localized resource string |
| Section | <title> / <summary> | Runs once, at the start / end |
| Section | <pageHeader> / <pageFooter> | Repeats every page |
| Section | <detail> | Repeats once per record |
| Tag | <band> | Print-flow section container |
| Tag | <frame> | Grouping container inside a band |
| Tag | <subreport> | Embeds another compiled report |
Where to Go From Here
This covers enough to build a working report end to end. Natural next steps: <group> for grouped and subtotaled data, charts and crosstabs for visual summaries, custom font extensions for non-default typefaces, and — if you outgrow embedding reports directly in your app — JasperReports Server or JasperReports IO for centralized scheduling and hosting, now under HCLSoftware following its mid-2026 acquisition from Cloud Software Group. The Jaspersoft community site is still the best place to dig into any of these further.