Expressions, Parameters, Fields & Variables
|
This content was generated with the assistance of AI and should be verified against the official documentation before being relied on in production. The reference material consulted while preparing these pages is listed in the section bibliography. |
Baseline date: 2026-10-09. A JasperReports template is mostly a list of expressions: the text of every field, the condition that decides whether a band prints, the value of every variable, the part of the query that changes between runs. This page covers the vocabulary those expressions are written in (parameters, fields, variables, resource bundle keys), what the engine does with them while it fills the report, and the languages and functions available. The running example is the monthly sales report of the Bookshelf Billing service. The structure of the template itself (bands, page format, elements) is in JRXML Report Design, the pipeline around it in JasperReports Architecture & Lifecycle, and grouping in Groups, Sorting & Filtering.
The examples were compiled and run with Java 21, JasperReports 7.0.8 (JRXML in the v7 format, with kind on every
element) and H2 2.3.232 standing in for PostgreSQL. Where a result below is a surprise, it comes from that run and is
marked as such.
The five kinds of reference
An expression is a piece of code in the report’s expression language (Java by default) that may contain references to five things. The engine compiles every expression of the template once, when the report is compiled, and evaluates it many times while the report is filled.
| Reference | Meaning |
|---|---|
|
The value of the parameter |
|
Only valid inside a query: the parameter’s value pasted into the query text as is, instead of bound as a value. Covered in its own section. |
|
The value of the field |
|
The current value of the variable |
|
The text for |
Each reference is replaced, in the generated evaluator code, by a typed value: a $F{amount} declared with
class="java.math.BigDecimal" is a BigDecimal in a Java expression, so $F{amount}.compareTo(...) compiles and a
mismatch between the declared class and what the data source returns fails at fill time. The expression’s own result must
be assignable to the class the element expects, which is java.lang.String for most text fields unless a class is
given. Pattern formatting (pattern="#,##0.00") applies only when the expression itself returns a number or a date. As
the report below shows, a number glued into a string with + is formatted by toString(), not by the pattern and not by
the locale.
A complete example
The report below has two parameters, five fields, a group, seven variables, a resource bundle and four different
evaluation times. The sections after it take the pieces apart. Maven coordinates for everything on this page (the
jasperreports-jdt compiler and jasperreports-functions are separate artifacts since 7.0, see Expression languages):
<properties>
<maven.compiler.release>21</maven.compiler.release>
<jasperreports.version>7.0.8</jasperreports.version>
</properties>
<dependencies>
<dependency>
<groupId>net.sf.jasperreports</groupId>
<artifactId>jasperreports</artifactId>
<version>${jasperreports.version}</version>
</dependency>
<dependency>
<groupId>net.sf.jasperreports</groupId>
<artifactId>jasperreports-pdf</artifactId>
<version>${jasperreports.version}</version>
</dependency>
<dependency>
<groupId>net.sf.jasperreports</groupId>
<artifactId>jasperreports-jdt</artifactId>
<version>${jasperreports.version}</version>
</dependency>
<dependency>
<groupId>net.sf.jasperreports</groupId>
<artifactId>jasperreports-functions</artifactId>
<version>${jasperreports.version}</version>
</dependency>
</dependencies>
src/main/resources/sales-report.jrxml:
<jasperReport name="sales-report" language="java" pageWidth="595" pageHeight="360"
columnWidth="515" leftMargin="40" rightMargin="40" topMargin="30" bottomMargin="30"
resourceBundle="messages" whenResourceMissingType="Key">
<parameter name="MIN_AMOUNT" class="java.math.BigDecimal">
<description><![CDATA[Rows below this amount are not shown]]></description>
<defaultValueExpression><![CDATA[new java.math.BigDecimal("0")]]></defaultValueExpression>
</parameter>
<parameter name="GENERATED_BY" class="java.lang.String"/>
<field name="category" class="java.lang.String"/>
<field name="title" class="java.lang.String"/>
<field name="quantity" class="java.lang.Integer"/>
<field name="amount" class="java.math.BigDecimal"/>
<field name="soldOn" class="java.util.Date"/>
<variable name="categoryTotal" class="java.math.BigDecimal" calculation="Sum" resetType="Group" resetGroup="category">
<expression><![CDATA[$F{amount}]]></expression>
</variable>
<variable name="grandTotal" class="java.math.BigDecimal" calculation="Sum">
<expression><![CDATA[$F{amount}]]></expression>
</variable>
<variable name="pageTotal" class="java.math.BigDecimal" calculation="Sum" resetType="Page">
<expression><![CDATA[$F{amount}]]></expression>
</variable>
<variable name="averageAmount" class="java.math.BigDecimal" calculation="Average">
<expression><![CDATA[$F{amount}]]></expression>
</variable>
<variable name="biggestSale" class="java.math.BigDecimal" calculation="Highest">
<expression><![CDATA[$F{amount}]]></expression>
</variable>
<variable name="bigSales" class="java.lang.Integer" calculation="Count">
<expression><![CDATA[$F{amount}.compareTo(new java.math.BigDecimal("50")) > 0 ? $F{title} : null]]></expression>
</variable>
<variable name="rowInGroup" class="java.lang.Integer" resetType="Group" resetGroup="category" calculation="Count">
<expression><![CDATA[$F{title}]]></expression>
</variable>
<group name="category">
<expression><![CDATA[$F{category}]]></expression>
<groupHeader>
<band height="22">
<element kind="textField" x="0" y="2" width="515" height="18" bold="true">
<expression><![CDATA[$R{label.category} + ": " + $F{category}]]></expression>
</element>
</band>
</groupHeader>
<groupFooter>
<band height="22">
<element kind="textField" x="0" y="2" width="300" height="18" hTextAlign="Right">
<expression><![CDATA[$R{label.subtotal} + " (" + $V{rowInGroup} + ")"]]></expression>
</element>
<element kind="textField" x="365" y="2" width="150" height="18" bold="true" hTextAlign="Right" pattern="#,##0.00">
<expression><![CDATA[$V{categoryTotal}]]></expression>
</element>
</band>
</groupFooter>
</group>
<title height="44">
<element kind="textField" x="0" y="0" width="515" height="22" fontSize="16" bold="true">
<expression><![CDATA[$R{report.title}]]></expression>
</element>
<element kind="textField" x="0" y="24" width="515" height="16" evaluationTime="Report">
<expression><![CDATA[$P{GENERATED_BY} + " | " + $V{REPORT_COUNT} + " rows, " + $V{bigSales} + " above 50.00"]]></expression>
</element>
</title>
<detail>
<band height="18">
<printWhenExpression><![CDATA[$F{amount}.compareTo($P{MIN_AMOUNT}) >= 0]]></printWhenExpression>
<element kind="textField" x="0" y="0" width="70" height="16">
<expression><![CDATA[DATEFORMAT($F{soldOn}, "yyyy-MM-dd")]]></expression>
</element>
<element kind="textField" x="75" y="0" width="225" height="16">
<expression><![CDATA[$F{title}]]></expression>
</element>
<element kind="textField" x="300" y="0" width="60" height="16" hTextAlign="Right">
<expression><![CDATA[$F{quantity}]]></expression>
</element>
<element kind="textField" x="365" y="0" width="150" height="16" hTextAlign="Right" pattern="#,##0.00">
<expression><![CDATA[$F{amount}]]></expression>
</element>
</band>
</detail>
<pageFooter height="24">
<element kind="textField" x="0" y="4" width="250" height="16" pattern="#,##0.00">
<expression><![CDATA["On this page: " + $V{pageTotal}]]></expression>
</element>
<element kind="textField" x="365" y="4" width="100" height="16" hTextAlign="Right">
<expression><![CDATA[$R{label.page} + " " + $V{PAGE_NUMBER} + " " + $R{label.of}]]></expression>
</element>
<element kind="textField" x="470" y="4" width="45" height="16" evaluationTime="Report">
<expression><![CDATA[$V{PAGE_NUMBER}]]></expression>
</element>
</pageFooter>
<summary height="40">
<element kind="textField" x="0" y="4" width="515" height="16" hTextAlign="Right" bold="true" pattern="#,##0.00">
<expression><![CDATA["Total " + $V{grandTotal} + ", average " + $V{averageAmount} + ", biggest " + $V{biggestSale}]]></expression>
</element>
</summary>
</jasperReport>
src/main/resources/messages.properties and messages_es.properties (Java reads properties files as UTF-8 since
Java 9, the escapes below are just one safe way to write accented characters):
# messages.properties
report.title=Bookshelf sales report
label.category=Category
label.subtotal=Subtotal
label.page=Page
label.of=of
# messages_es.properties
report.title=Informe de ventas de Bookshelf
label.category=Categor\u00eda
label.subtotal=Subtotal
label.page=P\u00e1gina
The bean the data source reads from, one getter per field:
package bookshelf;
import java.math.BigDecimal;
import java.util.Date;
public class SaleRow {
private final String category;
private final String title;
private final Integer quantity;
private final BigDecimal amount;
private final Date soldOn;
public SaleRow(String category, String title, Integer quantity, BigDecimal amount, Date soldOn) {
this.category = category;
this.title = title;
this.quantity = quantity;
this.amount = amount;
this.soldOn = soldOn;
}
public String getCategory() { return category; }
public String getTitle() { return title; }
public Integer getQuantity() { return quantity; }
public BigDecimal getAmount() { return amount; }
public Date getSoldOn() { return soldOn; }
}
A helper that compiles a template, fills it, and exports the result as PDF or as plain text. The text export is not
meant for users: it is a convenient way to see what the expressions produced. It lives in jasperreports itself, so
no extra artifact is needed:
package bookshelf;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.nio.charset.StandardCharsets;
import java.util.Map;
import net.sf.jasperreports.engine.JRDataSource;
import net.sf.jasperreports.engine.JRException;
import net.sf.jasperreports.engine.JasperCompileManager;
import net.sf.jasperreports.engine.JasperExportManager;
import net.sf.jasperreports.engine.JasperFillManager;
import net.sf.jasperreports.engine.JasperPrint;
import net.sf.jasperreports.engine.JasperReport;
import net.sf.jasperreports.engine.export.JRTextExporter;
import net.sf.jasperreports.export.SimpleExporterInput;
import net.sf.jasperreports.export.SimpleTextReportConfiguration;
import net.sf.jasperreports.export.SimpleWriterExporterOutput;
public final class ReportRunner {
private ReportRunner() {
}
public static JasperReport compile(String classpathResource) throws JRException, IOException {
try (InputStream in = ReportRunner.class.getResourceAsStream(classpathResource)) {
return JasperCompileManager.compileReport(in);
}
}
// params must be a mutable map: the engine adds the built-in parameters to it
public static JasperPrint fill(JasperReport report, Map<String, Object> params, JRDataSource data)
throws JRException {
return JasperFillManager.fillReport(report, params, data);
}
public static byte[] pdf(JasperPrint print) throws JRException {
return JasperExportManager.exportReportToPdf(print);
}
// plain-text rendering, handy for asserting on what an expression produced
public static String text(JasperPrint print) throws JRException {
var out = new ByteArrayOutputStream();
var exporter = new JRTextExporter();
exporter.setExporterInput(new SimpleExporterInput(print));
exporter.setExporterOutput(new SimpleWriterExporterOutput(out));
var configuration = new SimpleTextReportConfiguration();
configuration.setCharWidth(5.0f);
configuration.setCharHeight(10.0f);
exporter.setConfiguration(configuration);
exporter.exportReport();
return out.toString(StandardCharsets.UTF_8).lines().filter(line -> !line.isBlank())
.map(String::stripTrailing).reduce("", (a, b) -> a + b + "\n");
}
}
And the program that fills the report once per locale (jasperreports-pdf must be on the classpath for
exportReportToPdf):
package bookshelf;
import java.math.BigDecimal;
import java.nio.file.Files;
import java.nio.file.Path;
import java.time.LocalDate;
import java.time.ZoneOffset;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Locale;
import net.sf.jasperreports.engine.JRParameter;
import net.sf.jasperreports.engine.data.JRBeanCollectionDataSource;
public class SalesReportMain {
static Date day(int year, int month, int dayOfMonth) {
return Date.from(LocalDate.of(year, month, dayOfMonth).atStartOfDay().toInstant(ZoneOffset.UTC));
}
static List<SaleRow> sampleRows() {
return List.of(
new SaleRow("Fiction", "Don Quixote", 3, new BigDecimal("45.00"), day(2026, 9, 1)),
new SaleRow("Fiction", "Hamlet", 1, new BigDecimal("12.50"), day(2026, 9, 2)),
new SaleRow("Fiction", "Ulysses", 2, new BigDecimal("64.00"), day(2026, 9, 3)),
new SaleRow("Fiction", "Dubliners", 5, new BigDecimal("30.00"), day(2026, 9, 4)),
new SaleRow("Fiction", "The Trial", 1, new BigDecimal("9.90"), day(2026, 9, 5)),
new SaleRow("Science", "Cosmos", 2, new BigDecimal("40.00"), day(2026, 9, 6)),
new SaleRow("Science", "Principia", 1, new BigDecimal("88.00"), day(2026, 9, 7)),
new SaleRow("Science", "Opticks", 4, new BigDecimal("72.40"), day(2026, 9, 8)),
new SaleRow("Science", "Origin of Species", 2, new BigDecimal("33.00"), day(2026, 9, 9)),
new SaleRow("History", "SPQR", 1, new BigDecimal("22.00"), day(2026, 9, 10)),
new SaleRow("History", "Histories", 3, new BigDecimal("51.00"), day(2026, 9, 11)));
}
public static void main(String[] args) throws Exception {
var report = ReportRunner.compile("/sales-report.jrxml");
for (Locale locale : List.of(Locale.ENGLISH, Locale.forLanguageTag("es"))) {
var params = new HashMap<String, Object>();
params.put("GENERATED_BY", "Bookshelf Billing");
params.put("MIN_AMOUNT", new BigDecimal("10.00"));
params.put(JRParameter.REPORT_LOCALE, locale);
var print = ReportRunner.fill(report, params, new JRBeanCollectionDataSource(sampleRows()));
System.out.println("--- " + locale + ", " + print.getPages().size() + " pages");
System.out.print(ReportRunner.text(print));
Files.write(Path.of("sales-report-" + locale.getLanguage() + ".pdf"), ReportRunner.pdf(print));
}
}
}
Run with the JVM default locale set to English, the English run printed the following (whitespace condensed):
Bookshelf sales report
Bookshelf Billing | 11 rows, 4 above 50.00
Category: Fiction
2026-09-01 Don Quixote 3 45.00
2026-09-02 Hamlet 1 12.50
2026-09-03 Ulysses 2 64.00
2026-09-04 Dubliners 5 30.00
Subtotal (5) 161.40
Category: Science
2026-09-06 Cosmos 2 40.00
2026-09-07 Principia 1 88.00
2026-09-08 Opticks 4 72.40
2026-09-09 Origin of Species 2 33.00
Subtotal (4) 233.40
On this page: 394.80 Page 1 of 2
Category: History
2026-09-10 SPQR 1 22.00
2026-09-11 Histories 3 51.00
Subtotal (2) 73.00
Total 467.80, average 42.52727272727273, biggest 88.00
On this page: 73.00 Page 2 of 2
Four things in that output are worth checking against the template.
-
The title line shows
11 rows, 4 above 50.00although it sits above every row. Its text field hasevaluationTime="Report", so the engine evaluates it at the end of the fill and$V{REPORT_COUNT}and$V{bigSales}are the final values (see Evaluation time). -
The 9.90 row is missing from the detail lines, yet
Subtotal (5)counts five rows and161.40includes it. TheprintWhenExpressionof the detail band only decides whether the band is printed. Variables are calculated for every record the data source returns, printed or not. To leave a row out of the totals, filter it in the query or with a filter expression on the dataset, see Groups, Sorting & Filtering. -
The
Total 467.80, average 42.52727272727273, biggest 88.00line in the summary is a string concatenation: no pattern, no locale, and an unrounded average. Return the number itself from the expression and give the field apatternwhen the figure should be formatted. -
The page footer reads
Page 1 of 2. The second number is an element of its own withevaluationTime="Report".
The Spanish run (REPORT_LOCALE set to es) changes the labels from messages_es.properties and the decimal separator
of the numbers that are formatted by a pattern (45,00), and leaves the concatenated totals alone. A _es bundle that
omits a key falls back to the base bundle for that key, which is why label.of is not repeated in the Spanish file.
Derived from the JasperReports internationalization sample
and the JRVariable API.
Parameters
A parameter is declared with a name, a Java class and optionally a default value expression, a description and
forPrompting:
<parameter name="MIN_AMOUNT" class="java.math.BigDecimal">
<description><![CDATA[Rows below this amount are not shown]]></description>
<defaultValueExpression><![CDATA[new java.math.BigDecimal("0")]]></defaultValueExpression>
</parameter>
The caller supplies values in a java.util.Map<String, Object> passed to JasperFillManager.fillReport. A parameter the
map does not contain gets its default value expression, or null. The declared class is not checked when the map is
read: in the run, an Integer supplied for a BigDecimal parameter was accepted, and the failure (a
JRExpressionEvalException) came only when an expression used the value as a BigDecimal. forPrompting is a hint for
tools such as Jaspersoft Studio and JasperReports Server that ask the user for values. The engine itself does not
prompt, so forPrompting="false" marks a parameter a UI should not ask about, for example one that the application
always sets.
The map must be mutable. The engine adds the built-in parameters to it, so an immutable map, for example the one returned by Map.of, was rejected in the run with an
UnsupportedOperationException. Use a HashMap and copy the entries in, as SalesReportMain does.
Built-in parameters
Every report has these parameters without declaring them. The descriptions follow the
JRParameter API.
| Parameter | Type | What it holds |
|---|---|---|
|
|
All parameter values passed at fill time. Handy for handing the same set to a subreport. |
|
|
The connection given to |
|
|
The data source being iterated, supplied by the caller or created by the engine from a connection. |
|
|
Selects the resource bundle language and the number and date formats. |
|
|
The bundle behind |
|
|
Used to format dates and times. The host’s default zone when not supplied. |
|
|
Stops reading the data source after that many records. Useful for previews. |
|
|
When |
|
various |
Resource lookup, memory virtualization, number and date format factory, style templates, the scriptlet instance, the filling context, the template being filled, and the sort and filter settings of a dataset. |
The list is the set of String constants of JRParameter in 7.0.8. In the sales report, REPORT_LOCALE is set by the
program. In the query example below, REPORT_CONNECTION is used by an expression to show that the connection arrived.
Setting REPORT_TIME_ZONE explicitly is worth the line of code in a service: the default is the host’s zone, which on a
container is often UTC and not the zone of the customer who reads the invoice date.
Parameters in queries and the $P!{} clause
Inside a <query>, $P{CATEGORY} is turned into a ? placeholder and the value is bound by the JDBC driver, which is
what keeps the query safe from injection. $P!{ORDER_BY} is different: the parameter’s value is inserted into the
query text before it is sent, which is the only way to parameterize a column list or an ORDER BY, and the one way to
get an injection hole into a template. A second form, the $X{...} query clauses such as IN and EQUAL, builds
conditions from a parameter value and still binds values. Both are described in the query sample linked below.
src/main/resources/sales-query.jrxml:
<jasperReport name="sales-query" language="java" pageWidth="595" pageHeight="842"
columnWidth="515" leftMargin="40" rightMargin="40" topMargin="30" bottomMargin="30">
<parameter name="CATEGORY" class="java.lang.String"/>
<parameter name="ORDER_BY" class="java.lang.String">
<defaultValueExpression><![CDATA["title"]]></defaultValueExpression>
</parameter>
<query language="sql"><![CDATA[
SELECT title, amount FROM sale
WHERE category = $P{CATEGORY}
ORDER BY $P!{ORDER_BY}
]]></query>
<field name="title" class="java.lang.String"/>
<field name="amount" class="java.math.BigDecimal"/>
<title height="24">
<element kind="textField" x="0" y="0" width="515" height="20">
<expression><![CDATA["Connection is " + ($P{REPORT_CONNECTION} != null ? "set" : "missing") + ", order by " + $P{ORDER_BY}]]></expression>
</element>
</title>
<detail>
<band height="18">
<element kind="textField" x="0" y="0" width="400" height="16">
<expression><![CDATA[$F{title}]]></expression>
</element>
<element kind="textField" x="400" y="0" width="115" height="16" hTextAlign="Right" pattern="#,##0.00">
<expression><![CDATA[$F{amount}]]></expression>
</element>
</band>
</detail>
</jasperReport>
The Java side never passes caller text straight into the clause. It maps a key chosen by the caller to a SQL fragment it
owns. The connection settings come from environment variables; add
org.postgresql:postgresql (current release from Maven Central) for the real database, or any JDBC driver for another
one. The run below used an in-memory H2 database with the same sale table:
package bookshelf;
import java.sql.Connection;
import java.sql.DriverManager;
import java.util.HashMap;
import java.util.Map;
public class CategoryReport {
// The only values that may reach $P!{ORDER_BY}: the caller picks a key, never the SQL text.
private static final Map<String, String> ORDER_BY = Map.of(
"title", "title",
"amount", "amount DESC");
public static void main(String[] args) throws Exception {
String url = System.getenv("BILLING_DB_URL"); // e.g. jdbc:postgresql://localhost:5432/bookshelf
String user = System.getenv("BILLING_DB_USER");
String password = System.getenv("BILLING_DB_PASSWORD");
String sort = args.length > 0 ? args[0] : "title";
var report = ReportRunner.compile("/sales-query.jrxml");
try (Connection connection = DriverManager.getConnection(url, user, password)) {
var params = new HashMap<String, Object>();
params.put("CATEGORY", "Fiction");
params.put("ORDER_BY", ORDER_BY.getOrDefault(sort, "title"));
// a JDBC report takes a Connection instead of a JRDataSource
var print = net.sf.jasperreports.engine.JasperFillManager.fillReport(report, params, connection);
System.out.print(ReportRunner.text(print));
}
}
}
Connection is set, order by amount DESC
Ulysses 64.00
Dubliners 30.00
Hamlet 12.50
In the run, passing title; DROP TABLE sale as the sort key printed the report sorted by title: the lookup replaced it
with title, so the attacker’s text never reached the query.
To see what the lookup protects against, the same template was filled with the raw string as the value of ORDER_BY,
against the in-memory H2 database. The report filled without an error, and the next SELECT COUNT(*) FROM sale
failed with a "table not found" error: H2 ran both statements, so the sale table was gone. Whether another driver
executes a second statement depends on the driver, and should not be relied on as a defence.
Since 7.0.8 the engine has a switch for this (the change log lists it under 7.0.8, while the configuration reference
says 7.0.7, see Security Hardening). Setting
net.sf.jasperreports.query.parameter.clause.enabled to false makes any $P!{} in a query an error. It defaults
to true, and the run confirmed the error: Query parameter clause $P!{ORDER_BY} is not allowed. thrown as a
JRRuntimeException. The property can be set in a jasperreports.properties file on the classpath, or on the context
in code:
DefaultJasperReportsContext.getInstance()
.setProperty("net.sf.jasperreports.query.parameter.clause.enabled", "false");
A service whose templates are written by developers and reviewed can leave it on. A service that lets users upload or edit templates should turn it off, and see Security Hardening for the other switches.
Derived from the JasperReports query sample and the configuration reference.
Fields
A field declares the name and class of one value that the data source delivers per record:
<field name="amount" class="java.math.BigDecimal"/>
How the name is resolved depends on the data source. A JRBeanCollectionDataSource looks for a getter, so amount
calls getAmount(). A JRMapCollectionDataSource uses the map key, and a JDBC query matches the column label of the
result set. This is why a Java record fails as bean data: in the run, a record with accessors category() and
title() and no getters failed at fill time with Error retrieving field value from bean: category. Either give the
class JavaBean getters, as SaleRow does, or map the records to maps before filling. The other data sources are on
Data Sources & Query Executers.
Fields are read-only. An unmatched field name fails at fill time. A null value in a field is a null in the
expression, and a text field prints a null as the text null unless blankWhenNull="true" is set on the element. In
the run, a null date in a plain text field printed null and the same expression with blankWhenNull printed nothing.
Guard calculations against null explicitly, for example $F{amount} == null ? BigDecimal.ZERO : $F{amount}.
Variables
A variable holds a value that the engine updates while it walks the data source. The declaration is the most configurable element of a template:
<variable name="categoryTotal" class="java.math.BigDecimal" calculation="Sum"
resetType="Group" resetGroup="category">
<expression><![CDATA[$F{amount}]]></expression>
</variable>
For every record the engine evaluates the variable’s expression and combines the result with the previous value
according to calculation. When the event named by resetType happens, the variable goes back to its initial
value.
Built-in variables
| Variable | Meaning |
|---|---|
|
Current page. After the last page it holds the total number of pages, which is how "page X of Y" works. |
|
Current column in a multi-column layout. |
|
Number of records processed so far. At the end, the record count of the data source. |
|
Records processed on the current page or column. |
|
Records processed in the current group, a built-in for every declared group ( |
|
Page numbers of the master report when a document is built from several parts. Available only in elements with master evaluation time. |
Calculations
calculation takes one of: Nothing (the default, the value is just the latest expression result), Count (counts
non-null results, so $F{title} as the expression counts the rows that have a title), DistinctCount, Sum,
Average, Lowest, Highest, StandardDeviation, Variance, First (keeps the first value after each reset) and
System (the engine does not calculate anything and only keeps the value that user code, almost always a scriptlet, put
there from one record to the next). For Count, the
variable class must be numeric, while the expression need not be. Average, StandardDeviation and Variance create helper
variables behind the scenes; the average one is named like the variable with the suffix _SUM.
Reset and increment types
resetType says when the variable returns to its initial value: Report (the default: once, at the start), Page,
Column, Group (with resetGroup) or None. incrementType says when the variable’s expression is evaluated and the
calculation applied: None (the default, with every record), or Report, Page, Column and Group (with
incrementGroup) for the less common variables that should be updated only on that event. The API documentation says that
None as a reset type means the variable is never initialized from its initial value expression and holds only what its
expression returns. A Sum with resetType="None" printed twice the amount of each row in the run, instead of a running
total, so the combination is not used on this page: use Report for a running total.
The initialValueExpression gives the value after a reset. For a Count it must be numeric and is the starting
count. For a Sum the run showed the following surprise: a variable with an initial value of 100.00 printed exactly
the same figures as one without, because the first value to arrive replaces the initial value instead of being added to
it. To start a sum from an opening balance, add the balance in the expression that displays the value.
src/main/resources/variables.jrxml shows five calculations, a group-scoped variable, an increment type, an initial
value, an import of a helper class and a percentage computed against a total that is not yet known:
<jasperReport name="variables" language="java" pageWidth="595" pageHeight="842"
columnWidth="515" leftMargin="40" rightMargin="40" topMargin="30" bottomMargin="30">
<import>bookshelf.Pricing</import>
<field name="category" class="java.lang.String"/>
<field name="title" class="java.lang.String"/>
<field name="amount" class="java.math.BigDecimal"/>
<variable name="runningTotal" class="java.math.BigDecimal" calculation="Sum">
<expression><![CDATA[$F{amount}]]></expression>
</variable>
<variable name="withOpeningBalance" class="java.math.BigDecimal" calculation="Sum">
<expression><![CDATA[$F{amount}]]></expression>
<initialValueExpression><![CDATA[new java.math.BigDecimal("100.00")]]></initialValueExpression>
</variable>
<variable name="categories" class="java.lang.Integer" calculation="DistinctCount">
<expression><![CDATA[$F{category}]]></expression>
</variable>
<variable name="categoryTotal" class="java.math.BigDecimal" calculation="Sum" resetType="Group" resetGroup="category">
<expression><![CDATA[$F{amount}]]></expression>
</variable>
<variable name="firstInGroup" class="java.lang.String" calculation="First" resetType="Group" resetGroup="category">
<expression><![CDATA[$F{title}]]></expression>
</variable>
<variable name="groupsSeen" class="java.lang.Integer" calculation="Count" incrementType="Group" incrementGroup="category">
<expression><![CDATA[$F{category}]]></expression>
</variable>
<variable name="lowest" class="java.math.BigDecimal" calculation="Lowest">
<expression><![CDATA[$F{amount}]]></expression>
</variable>
<variable name="priceBand" class="java.lang.String">
<expression><![CDATA[Pricing.band($F{amount})]]></expression>
</variable>
<group name="category">
<expression><![CDATA[$F{category}]]></expression>
<groupHeader>
<band height="20">
<element kind="textField" x="0" y="0" width="515" height="16" evaluationTime="Group" evaluationGroup="category">
<expression><![CDATA[$F{category} + ": first=" + $V{firstInGroup} + ", total=" + $V{categoryTotal} + ", groupsSeen=" + $V{groupsSeen}]]></expression>
</element>
</band>
</groupHeader>
</group>
<detail>
<band height="18">
<element kind="textField" x="0" y="0" width="415" height="16">
<expression><![CDATA[$F{title} + " " + $F{amount} + " " + $V{priceBand} + " running=" + $V{runningTotal} + " opening=" + $V{withOpeningBalance}]]></expression>
</element>
<element kind="textField" x="420" y="0" width="95" height="16" hTextAlign="Right" evaluationTime="Auto" pattern="0.0%">
<expression><![CDATA[$F{amount}.doubleValue() / $V{runningTotal}.doubleValue()]]></expression>
</element>
</band>
</detail>
<summary height="20">
<element kind="textField" x="0" y="0" width="515" height="16">
<expression><![CDATA["categories=" + $V{categories} + ", lowest=" + $V{lowest} + ", rows=" + $V{REPORT_COUNT}]]></expression>
</element>
</summary>
</jasperReport>
The helper class imported by the template:
package bookshelf;
import java.math.BigDecimal;
public final class Pricing {
private static final BigDecimal PREMIUM_FROM = new BigDecimal("50");
private enum Band { FREE, REGULAR, PREMIUM }
private Pricing() {
}
// Java 21 is fine here: this class is compiled by javac, not by the report compiler
public static String band(BigDecimal amount) {
var band = amount.signum() <= 0 ? Band.FREE
: amount.compareTo(PREMIUM_FROM) > 0 ? Band.PREMIUM : Band.REGULAR;
return switch (band) {
case FREE -> "free";
case REGULAR -> "regular";
case PREMIUM -> "premium";
};
}
}
And the program that fills it with the same sample rows:
package bookshelf;
import java.util.HashMap;
import net.sf.jasperreports.engine.data.JRBeanCollectionDataSource;
public class VariablesReport {
public static void main(String[] args) throws Exception {
var report = ReportRunner.compile("/variables.jrxml");
var print = ReportRunner.fill(report, new HashMap<>(),
new JRBeanCollectionDataSource(SalesReportMain.sampleRows()));
System.out.print(ReportRunner.text(print));
}
}
Fiction: first=Don Quixote, total=161.40, groupsSeen=1
Don Quixote 45.00 regular running=45.00 opening=45.00 9.6%
Hamlet 12.50 regular running=57.50 opening=57.50 2.7%
Ulysses 64.00 premium running=121.50 opening=121.50 13.7%
Dubliners 30.00 regular running=151.50 opening=151.50 6.4%
The Trial 9.90 regular running=161.40 opening=161.40 2.1%
Science: first=Cosmos, total=233.40, groupsSeen=2
Cosmos 40.00 regular running=201.40 opening=201.40 8.6%
Principia 88.00 premium running=289.40 opening=289.40 18.8%
Opticks 72.40 premium running=361.80 opening=361.80 15.5%
Origin of Species 33.00 regular running=394.80 opening=394.80 7.1%
History: first=SPQR, total=73.00, groupsSeen=3
SPQR 22.00 regular running=416.80 opening=416.80 4.7%
Histories 51.00 premium running=467.80 opening=467.80 10.9%
categories=3, lowest=9.90, rows=11
opening equals running in every row, which is the Sum and initial value behaviour described above. groupsSeen
grows by one at each group because its increment type is Group. The Fiction header line already shows the
total of the whole group, 161.40: that element has evaluationTime="Group", covered in the next section. The
percentage column is the Auto evaluation time at work.
Derived from the CalculationEnum,
ResetTypeEnum and
IncrementTypeEnum API pages.
Resource bundles
A template names a bundle with the resourceBundle attribute of the root element, and the text elements use
$R{key}. At fill time the engine loads the bundle for REPORT_LOCALE, or uses a ResourceBundle object passed as
REPORT_RESOURCE_BUNDLE. What happens when a key is missing is a template setting: whenResourceMissingType takes
Null, Empty, Key (print the key itself, which is what the sales report uses so that a gap is visible) or Error.
There is a trap in the standard Java bundle lookup that the run exposed. When the English run asked for a bundle on a
machine whose JVM default locale was Spanish, it printed the Spanish text, because ResourceBundle tries the default
locale’s file before the base file when the requested locale has no file of its own. Run with the default
locale set to English, the English run printed the base file. A service that must always answer in a fixed
language should ship an explicit messages_en.properties, or pass its own ResourceBundle through
REPORT_RESOURCE_BUNDLE. How fonts and right-to-left scripts fit in is on
Fonts & Internationalization.
Evaluation time
Most expressions are evaluated "now": when the band they sit in is being filled. evaluationTime on a text field (or image,
or any element with an expression) postpones the evaluation, and the engine leaves a placeholder in the page until the
moment arrives:
| Value | Evaluated |
|---|---|
|
When the band is filled. The default. |
|
At the end of the report. For totals shown at the top and for the page count in "page X of Y". |
|
When the page being filled ends. For "records on this page" style values placed in the page header. |
|
When the column ends. |
|
After the group named by |
|
At the end of the band, so values changed by later elements of the same band are visible. |
|
Each variable in the expression at the time of its own reset type, fields now. For expressions that mix values of different scopes, such as a percentage of a total. The API documentation advises against it when another value suffices, because it can cost performance. |
|
When the master report ends, for the master page variables. |
The classic pair is the one in the sales report’s footer: the current page is evaluated now, the total only when the last page is known.
<element kind="textField" x="365" y="4" width="100" height="16" hTextAlign="Right">
<expression><![CDATA[$R{label.page} + " " + $V{PAGE_NUMBER} + " " + $R{label.of}]]></expression>
</element>
<element kind="textField" x="470" y="4" width="45" height="16" evaluationTime="Report">
<expression><![CDATA[$V{PAGE_NUMBER}]]></expression>
</element>
Two limits are worth knowing. The deferred element is laid out before its value exists, so reserve room for the longest
value it can take: the footer cannot be reflowed once the total is known. And a field is not remembered per record: in
the run, a text field in the detail band with evaluationTime="Report" and the expression $F{title} printed the
title of the last record on every row. Use a variable, which the engine can evaluate at the right moment, when a value must be correct in
a deferred element. The page footer and the table of contents of a report book
use the same mechanism, see the
table of contents sample, where the
contents part is evaluated after the other parts.
Derived from the EvaluationTimeEnum API
and the table of contents sample above.
Expression languages
The language attribute of the report selects the language its expressions are written in.
| Language | Artifact (7.0.8) | Notes |
|---|---|---|
|
|
Compiled in memory by the Eclipse JDT compiler (ECJ 3.21.0 in 7.0.8). Fast at run time, type-checked at compile time. |
|
|
Groovy 4. Terser: |
|
|
Mozilla Rhino (1.8.1 in 7.0.8), interpreted from compiled script classes. Useful where the report authors know JavaScript and not Java. |
All three understand the same $P{}, $F{}, $V{} and $R{} references. The check below puts one condition into
a small template for each language and fills it with two sample rows. In the run all three printed the same text:
package bookshelf;
import java.io.ByteArrayInputStream;
import java.math.BigDecimal;
import java.nio.charset.StandardCharsets;
import java.util.HashMap;
import net.sf.jasperreports.engine.JasperCompileManager;
import net.sf.jasperreports.engine.data.JRBeanCollectionDataSource;
public class LanguageCheck {
private static final String TEMPLATE = """
<jasperReport name="size-by-language" language="%s" pageWidth="595" pageHeight="842"
columnWidth="515" leftMargin="40" rightMargin="40" topMargin="30" bottomMargin="30">
<parameter name="LIMIT" class="java.math.BigDecimal"/>
<field name="title" class="java.lang.String"/>
<field name="amount" class="java.math.BigDecimal"/>
<detail>
<band height="18">
<element kind="textField" x="0" y="0" width="515" height="16">
<expression><![CDATA[%s]]></expression>
</element>
</band>
</detail>
</jasperReport>
""";
static String run(String language, String expression) throws Exception {
String jrxml = TEMPLATE.formatted(language, expression);
var report = JasperCompileManager.compileReport(
new ByteArrayInputStream(jrxml.getBytes(StandardCharsets.UTF_8)));
var params = new HashMap<String, Object>();
params.put("LIMIT", new BigDecimal("50"));
var print = ReportRunner.fill(report, params,
new JRBeanCollectionDataSource(SalesReportMain.sampleRows().subList(1, 3)));
return ReportRunner.text(print).strip().replace("\n", " | ");
}
public static void main(String[] args) throws Exception {
System.out.println("java " + run("java",
"$F{title} + ($F{amount}.compareTo($P{LIMIT}) > 0 ? \" is big\" : \" is small\")"));
System.out.println("groovy " + run("groovy",
"$F{title} + ($F{amount} > $P{LIMIT} ? \" is big\" : \" is small\")"));
System.out.println("javascript " + run("javascript",
"$F{title} + ($F{amount} > $P{LIMIT} ? \" is big\" : \" is small\")"));
}
}
java Hamlet is small | Ulysses is big
groovy Hamlet is small | Ulysses is big
javascript Hamlet is small | Ulysses is big
(add net.sf.jasperreports:jasperreports-groovy and net.sf.jasperreports:jasperreports-javascript, both 7.0.8, to the
dependencies above to run it). Derived from the
Groovy compiler sample and the
JavaScript compiler sample.
What changed since the books
The books consulted for this section (B2 chapter 2 and B3 chapter 3) describe compiling expressions with the JDK’s
tools.jar, a JDK 1.3 compiler setting, the Jikes compiler and BeanShell scripts. That setup is outdated.
tools.jar has not shipped with the JDK since Java 9, and the compilers that the 7.0 line provides are the three in the
table above, each in its own artifact (jasperreports-jdt, jasperreports-groovy, jasperreports-javascript). The build-time alternative to compiling at run time is the
jasperreports-maven-plugin (available for 7.0.6 and later). In the run, its compile goal produced .jasper files
from the sales template, with jasperreports, jasperreports-jdt and jasperreports-functions declared as the plugin’s
own dependencies (it needs the same compiler artifacts the application would). The plugin and compile-time caching are described on
Large Reports & Performance.
Two details of the Java compiler matter in practice. First, its language level is Java 8 by default: the
default.jasperreports.properties file in the 7.0.8 jar sets the Eclipse compiler’s source, compliance and target
platform to 1.8, and in the run an inline switch expression and an instanceof pattern were both rejected as compile errors. That is why
the variables.jrxml above calls Pricing.band(...) from an imported class, compiled by the application’s own Java 21
javac, instead of writing the logic inline: the template pulls it in with <import>, and anything can then be called
from an expression. For heavier logic a scriptlet is the other option, see
Scriptlets, Properties & Extensions. Second,
without jasperreports-jdt on the classpath, the run fell back to the JDK compiler, which needs a JDK rather than a JRE
at run time, and the sales report then failed to compile with cannot find symbol for the built-in function
DATEFORMAT. Keep jasperreports-jdt and jasperreports-functions together in a Java-expression project.
Built-in functions
Since 5.1.2 the library has had a set of spreadsheet-like functions that can be called directly from expressions, in
all three languages, with no import. In 7.0 they live in the net.sf.jasperreports:jasperreports-functions
artifact. The sales report already uses one, DATEFORMAT($F{soldOn}, "yyyy-MM-dd"), to format a date without a
SimpleDateFormat. The reference page groups them in categories:
| Category | Examples |
|---|---|
Date and time |
|
Logical |
|
Mathematical |
|
Text |
|
Report |
|
Calls the run exercised, with the sample rows: DAYS(DATE(2026, 9, 1), $F{soldOn}), LEFT($F{title}, 3),
UPPER($F{title}), ROUND_HALF_UP($F{amount}.doubleValue() / 3, 1) and IF($F{amount} > 50, "big", "small") in Groovy
and JavaScript (in Java the condition has to be a boolean, so IF($F{amount}.doubleValue() > 50, "big", "small")). The functions
are not a substitute for typed code: they take and return Object and Number types, so a Java expression often needs a
doubleValue() or a cast to feed them. The complete signature list is in the function reference below, which is
also the place to check a function’s argument order before using it.
Derived from the functions sample and the function reference.
Java 21 and the examples
On the application side, the code on this page uses var, a text block for the JRXML in LanguageCheck,
List.of and Map.of for read-only data, an enum inside a switch expression in Pricing, and try-with-resources
for streams and connections. The two places where the engine, not the language, limits the style are the ones found in
the run: the parameter map must be mutable, and the bean data source needs getters, so records are not usable
as-is. See the language reference for
switch expressions and
records. Exposing the report as an HTTP endpoint with
the locale taken from the request is shown in
Getting Started with Spring Boot, and the query side of
a report in Generating Reports from Databases & Services.
References
-
JRXML Report Design in this section
-
Groups, Sorting & Filtering in this section