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

$P{name}

The value of the parameter name, passed by the caller (or its default) before filling starts. The same value for the whole run.

$P!{name}

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.

$F{name}

The value of the field name for the current record of the data source.

$V{name}

The current value of the variable name, either built in or computed by a calculation over the records so far.

$R{key}

The text for key in the report’s resource bundle, in the language of the current locale.

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.00 although it sits above every row. Its text field has evaluationTime="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 and 161.40 includes it. The printWhenExpression of 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.00 line 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 a pattern when the figure should be formatted.

  • The page footer reads Page 1 of 2. The second number is an element of its own with evaluationTime="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.

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

REPORT_PARAMETERS_MAP

Map

All parameter values passed at fill time. Handy for handing the same set to a subreport.

REPORT_CONNECTION

java.sql.Connection

The connection given to fillReport, or null when a JRDataSource was given instead. Also how a subreport reuses the master report’s connection.

REPORT_DATA_SOURCE

JRDataSource

The data source being iterated, supplied by the caller or created by the engine from a connection.

REPORT_LOCALE

java.util.Locale

Selects the resource bundle language and the number and date formats. Locale.getDefault() when not supplied.

REPORT_RESOURCE_BUNDLE

java.util.ResourceBundle

The bundle behind $R{...}, either passed in or built by the engine from the template’s resourceBundle base name.

REPORT_TIME_ZONE

java.util.TimeZone

Used to format dates and times. The host’s default zone when not supplied.

REPORT_MAX_COUNT

Integer

Stops reading the data source after that many records. Useful for previews.

IS_IGNORE_PAGINATION

Boolean

When Boolean.TRUE, the engine ignores the page-break settings and produces one very large page. Meant for flow-oriented output such as web pages.

REPORT_CLASS_LOADER, REPORT_VIRTUALIZER, REPORT_FORMAT_FACTORY, REPORT_TEMPLATES, REPORT_SCRIPTLET, REPORT_CONTEXT, JASPER_REPORT, JASPER_REPORTS_CONTEXT, SORT_FIELDS, FILTER

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.

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

PAGE_NUMBER

Current page. After the last page it holds the total number of pages, which is how "page X of Y" works.

COLUMN_NUMBER

Current column in a multi-column layout.

REPORT_COUNT

Number of records processed so far. At the end, the record count of the data source.

PAGE_COUNT, COLUMN_COUNT

Records processed on the current page or column.

<group name>_COUNT

Records processed in the current group, a built-in for every declared group (category_COUNT in the sales report).

MASTER_CURRENT_PAGE, MASTER_TOTAL_PAGES

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

Now

When the band is filled. The default.

Report

At the end of the report. For totals shown at the top and for the page count in "page X of Y".

Page

When the page being filled ends. For "records on this page" style values placed in the page header.

Column

When the column ends.

Group

After the group named by evaluationGroup breaks. For a group total printed in the group header.

Band

At the end of the band, so values changed by later elements of the same band are visible.

Auto

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.

Master

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

java (default)

net.sf.jasperreports:jasperreports-jdt

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

net.sf.jasperreports:jasperreports-groovy

Groovy 4. Terser: $F{amount} > $P{LIMIT} works on BigDecimal without compareTo. Dynamically typed, so more errors appear at fill time.

javascript

net.sf.jasperreports:jasperreports-javascript

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

DATE, DATEFORMAT, DAY, MONTH, YEAR, DAYS, EDATE, NETWORKDAYS, NOW, TODAY, WEEKDAY, WORKDAY

Logical

IF, AND, OR, NOT, EQUALS, TRUE, FALSE

Mathematical

ABS, CEIL, FLOOR, MAX, MIN, SUM, PRODUCT, SQRT, ISEVEN, ISODD, ROUND_HALF_UP, ROUND_HALF_EVEN, ROUND_DOWN

Text

CONCATENATE, LEFT, RIGHT, MID, LEN, LOWER, UPPER, PROPER, TRIM, SUBSTITUTE, REPLACE, FIND, TEXT

Report

ORIGINAL_DATA_SOURCE, RESOURCE_DATA, SUB_DATA_SOURCE

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.