PDF Export Configuration

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 filled report is a JasperPrint: pages of positioned elements with no format yet. Turning it into a PDF is the job of the exporter, and almost everything that makes a PDF a PDF (its title, its encryption, its conformance level, whether a screen reader can follow it) is decided there, not in the template. This page covers the JasperReports 7.0.8 PDF exporter and the two configuration objects that drive it, using the invoice summary of the Bookshelf Billing service as the running example. The design of the template, the data sources and the Maven setup are covered by other pages, see JasperReports Architecture & Lifecycle, JRXML Report Design and Getting Started with Spring Boot.

The examples were compiled and run with Java 21 as the language level (maven.compiler.release 21), JasperReports 7.0.8, jasperreports-pdf 7.0.8, jasperreports-fonts 7.0.8, jasperreports-jdt 7.0.8 and Bouncy Castle 1.86. The resulting PDF files were opened and inspected with the Python library pypdf. Results that come from that run, and that the official documentation does not state, are marked as such. The PDF/A files were not validated with veraPDF, so none of the PDF/A statements below is a conformance certificate.

The exporter API in 7.0

Older JasperReports material, including the books this section consulted, drives an exporter with a parameter map: JRExporterParameter and JRPdfExporterParameter constants passed to setParameter, and iText 1.x or 2.1.7 behind the PDF output. None of that exists in 7.0.8. The exporter is configured with typed objects, and PDF generation lives in its own artifact, jasperreports-pdf, under the package net.sf.jasperreports.pdf.

Concern JasperReports 7.0.8 API

The exporter

net.sf.jasperreports.pdf.JRPdfExporter, created with new JRPdfExporter() or with a JasperReportsContext

What to export

SimpleExporterInput, built from one JasperPrint, from a list of them (SimpleExporterInput.getInstance(list)), or from a file, URL or stream holding a serialized JasperPrint

Where to write

SimpleOutputStreamExporterOutput, built from an OutputStream, a File or a file name

Per-document settings

SimplePdfExporterConfiguration (implements PdfExporterConfiguration)

Per-report settings

SimplePdfReportConfiguration (implements PdfReportConfiguration)

Defaults for everything above

Configuration properties named net.sf.jasperreports.export.pdf.* and net.sf.jasperreports.export.pdfa.*

JRPdfExporter with its input, output, two configuration objects, producer factory and tagger factory

The "per-document" and "per-report" split follows what the output is made of. A PDF file has one encryption dictionary, one set of metadata and one conformance level, so those live in SimplePdfExporterConfiguration. A JasperPrint can be one of several reports exported into the same file (batch mode), and each of them has its own page range, page offsets and bookmark setting, so those live in SimplePdfReportConfiguration. Both extend a base class that adds overrideHints, discussed below.

Two shortcuts exist for the case where defaults are enough. JasperExportManager.exportReportToPdf(print) returns the bytes, and exportReportToPdfStream and exportReportToPdfFile write them elsewhere. In the run used for this page the first returned a valid PDF with every setting at its default. As soon as one setting needs to change, build the exporter yourself, as in the next section.

Dependencies

The PDF exporter is not in the core jasperreports artifact. Add jasperreports-pdf, and jasperreports-fonts for an embeddable font (see below), and a compiler for the expressions, as described in Getting Started with Spring Boot.

<dependencies>
  <dependency>
    <groupId>net.sf.jasperreports</groupId>
    <artifactId>jasperreports</artifactId>
    <version>7.0.8</version>
  </dependency>
  <dependency>
    <groupId>net.sf.jasperreports</groupId>
    <artifactId>jasperreports-pdf</artifactId>
    <version>7.0.8</version>
  </dependency>
  <dependency>
    <groupId>net.sf.jasperreports</groupId>
    <artifactId>jasperreports-fonts</artifactId>
    <version>7.0.8</version>
  </dependency>
  <dependency>
    <groupId>net.sf.jasperreports</groupId>
    <artifactId>jasperreports-jdt</artifactId>
    <version>7.0.8</version>
  </dependency>
  <!-- only needed when the exporter encrypts the PDF, see "Encryption and permissions" -->
  <dependency>
    <groupId>org.bouncycastle</groupId>
    <artifactId>bcprov-jdk18on</artifactId>
    <version>1.86</version>
  </dependency>
</dependencies>

jasperreports-pdf brings OpenPDF as a regular compile dependency, in the Jaspersoft build 1.3.43.jaspersoft.2. That is the library that writes the PDF bytes, see The OpenPDF producer.

A first export

The template is a v7 JRXML invoice summary: namespace-free, with kind on each element, and with the title and summary as bands directly under the report. Its default style names the font DejaVu Sans, which jasperreports-fonts provides as a font extension that is embedded in the PDF. That choice matters for PDF/A, and the reason is in PDF/A archival output.

<jasperReport name="invoice-summary" language="java" pageWidth="595" pageHeight="842"
              columnWidth="515" leftMargin="40" rightMargin="40" topMargin="40" bottomMargin="40">
  <style name="Base" default="true" fontName="DejaVu Sans" fontSize="10"/>
  <parameter name="CUSTOMER" 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"/>
  <variable name="total" calculation="Sum" class="java.math.BigDecimal">
    <expression><![CDATA[$F{amount}]]></expression>
  </variable>
  <title height="60">
    <element kind="staticText" x="0" y="0" width="515" height="30" fontSize="18" bold="true">
      <text><![CDATA[Bookshelf Billing: invoice summary]]></text>
    </element>
    <element kind="textField" x="0" y="34" width="515" height="20">
      <expression><![CDATA["Customer: " + $P{CUSTOMER}]]></expression>
    </element>
  </title>
  <columnHeader height="22">
    <element kind="staticText" x="0" y="0" width="300" height="20" bold="true">
      <text><![CDATA[Title]]></text>
    </element>
    <element kind="staticText" x="300" y="0" width="60" height="20" bold="true" hTextAlign="Right">
      <text><![CDATA[Qty]]></text>
    </element>
    <element kind="staticText" x="365" y="0" width="150" height="20" bold="true" hTextAlign="Right">
      <text><![CDATA[Amount]]></text>
    </element>
  </columnHeader>
  <detail>
    <band height="20">
      <element kind="textField" x="0" y="0" width="300" height="18">
        <expression><![CDATA[$F{title}]]></expression>
      </element>
      <element kind="textField" x="300" y="0" width="60" height="18" hTextAlign="Right">
        <expression><![CDATA[$F{quantity}]]></expression>
      </element>
      <element kind="textField" x="365" y="0" width="150" height="18" hTextAlign="Right" pattern="#,##0.00">
        <expression><![CDATA[$F{amount}]]></expression>
      </element>
    </band>
  </detail>
  <summary height="30">
    <element kind="textField" x="365" y="6" width="150" height="20" bold="true" hTextAlign="Right" pattern="#,##0.00">
      <expression><![CDATA[$V{total}]]></expression>
    </element>
  </summary>
</jasperReport>

The data is a JavaBean. A Java record is not accepted by JRBeanCollectionDataSource in 7.0.8: the run for this page failed when the lines were records, and succeeded with the class below.

package billing;

import java.math.BigDecimal;

/** A JavaBean: JRBeanCollectionDataSource reads getters, and a Java record is not accepted. */
public class InvoiceLine {

    private final String title;
    private final int quantity;
    private final BigDecimal amount;

    public InvoiceLine(String title, int quantity, BigDecimal amount) {
        this.title = title;
        this.quantity = quantity;
        this.amount = amount;
    }

    public String getTitle() {
        return title;
    }

    public int getQuantity() {
        return quantity;
    }

    public BigDecimal getAmount() {
        return amount;
    }
}

The class below compiles the template once, fills it, and exports with whatever configuration the caller supplies. The exporter is created for each call because it keeps per-export state in its fields. The parameter map is a HashMap: Map.of(...) is rejected as the fill parameters in 7.0.8.

package billing;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import net.sf.jasperreports.engine.JRException;
import net.sf.jasperreports.engine.JasperCompileManager;
import net.sf.jasperreports.engine.JasperFillManager;
import net.sf.jasperreports.engine.JasperPrint;
import net.sf.jasperreports.engine.JasperReport;
import net.sf.jasperreports.engine.data.JRBeanCollectionDataSource;
import net.sf.jasperreports.export.SimpleExporterInput;
import net.sf.jasperreports.export.SimpleOutputStreamExporterOutput;
import net.sf.jasperreports.pdf.JRPdfExporter;
import net.sf.jasperreports.pdf.SimplePdfExporterConfiguration;
import net.sf.jasperreports.pdf.SimplePdfReportConfiguration;

public final class InvoicePdfExporter {

    private final JasperReport report;

    public InvoicePdfExporter() throws JRException, IOException {
        try (InputStream template = getClass().getResourceAsStream("/invoice-summary.jrxml")) {
            this.report = JasperCompileManager.compileReport(template);
        }
    }

    public JasperPrint fill(String customer, List<InvoiceLine> lines) throws JRException {
        Map<String, Object> parameters = new HashMap<>();   // Map.of(...) is rejected by the fill
        parameters.put("CUSTOMER", customer);
        return JasperFillManager.fillReport(report, parameters, new JRBeanCollectionDataSource(lines));
    }

    /** A new exporter per call: the exporter keeps per-export state in its fields. */
    public byte[] export(JasperPrint print, SimplePdfExporterConfiguration exporterConfig,
                         SimplePdfReportConfiguration reportConfig) throws JRException {
        ByteArrayOutputStream out = new ByteArrayOutputStream();
        JRPdfExporter exporter = new JRPdfExporter();
        exporter.setExporterInput(new SimpleExporterInput(print));
        exporter.setExporterOutput(new SimpleOutputStreamExporterOutput(out));
        exporter.setConfiguration(exporterConfig);
        if (reportConfig != null) {
            exporter.setConfiguration(reportConfig);
        }
        exporter.exportReport();
        return out.toByteArray();
    }
}

The five steps are always the same: create the exporter, set the input, set the output, set the configuration object or objects, call exportReport(). setConfiguration is overloaded for the two configuration types, and both can be set on the same exporter. A SimpleOutputStreamExporterOutput built from an OutputStream did not close that stream in the run for this page, so the caller owns it. That is the behavior to rely on in a Spring controller that writes to the response stream, and the controller should still close or flush it as the framework requires.

Where a setting comes from

Every setting has up to four sources, from the broadest to the narrowest:

  1. A configuration property in jasperreports.properties or set on a JasperReportsContext, for example net.sf.jasperreports.export.pdf.compressed=true. These are the defaults, scoped globally or per context.

  2. A property on the report itself, as an exporter hint: a <property name="..." value="..."/> element in the JRXML, for example net.sf.jasperreports.export.pdf.metadata.title. The hint travels with the template, so a report designer can give a template a title without a deployment.

  3. A setting on the configuration object that the code passes to the exporter.

  4. A property on an individual element, which only a few properties accept (the tag language, for instance).

The reference of properties lists the scope of each property (global, context, report, element) and which ones are the default of a configuration setting. The property names match the configuration setters, so the two are interchangeable: setCompressed and net.sf.jasperreports.export.pdf.compressed, setPdfaConformance and net.sf.jasperreports.export.pdfa.conformance, setTagged and net.sf.jasperreports.export.pdf.tagged, and so on.

The interesting question is what wins when a report hint and the configuration object disagree. The run for this page put a title and an author as hints in a template and a different title in the configuration object:

  • With no overrideHints set, the title from the configuration object won, and the author, which the object did not set, came from the hint. Only the settings the object actually sets take part in the override.

  • With setOverrideHints(false), the title from the template hint won.

  • With setOverrideHints(true), the result was the same as the default.

In practice: set what the service must always control (encryption, conformance, producer) in code, leave presentation details such as the title to the template, and set overrideHints to false on the configuration object when the template author should be able to override the service’s defaults.

Export profiles for Bookshelf Billing

The service needs a handful of recurring variations: an e-mailed document, a copy protected from editing, an archival copy and a print-shop copy. Keeping each as a named factory method makes the choice explicit at the call site and keeps the settings reviewable in one file. Every setting used here is explained in the sections that follow.

package billing;

import java.nio.file.Path;

import net.sf.jasperreports.pdf.SimplePdfExporterConfiguration;
import net.sf.jasperreports.pdf.type.PdfPermissionsEnum;
import net.sf.jasperreports.pdf.type.PdfPrintScalingEnum;
import net.sf.jasperreports.pdf.type.PdfVersionEnum;
import net.sf.jasperreports.pdf.type.PdfaConformanceEnum;

/** Named export profiles for the Bookshelf Billing documents. */
public final class PdfProfiles {

    private PdfProfiles() {
    }

    /** Metadata, PDF 1.7 and object-stream compression: the profile for e-mailed documents. */
    public static SimplePdfExporterConfiguration standard() {
        SimplePdfExporterConfiguration config = new SimplePdfExporterConfiguration();
        config.setMetadataTitle("Invoice summary");
        config.setMetadataAuthor("Bookshelf Billing");
        config.setMetadataSubject("Invoice summary for a customer");
        config.setMetadataKeywords("invoice, bookshelf");
        config.setMetadataCreator("Bookshelf Billing service");
        config.setMetadataProducer("Bookshelf Billing 1.0");
        config.setDisplayMetadataTitle(true);
        config.setPdfVersion(PdfVersionEnum.VERSION_1_7);
        config.setCompressed(true);
        return config;
    }

    /** Encrypted with a 128-bit key and no user password: anyone may open it, only print and copy are allowed. */
    public static SimplePdfExporterConfiguration restricted(String ownerPassword) {
        SimplePdfExporterConfiguration config = standard();
        config.setEncrypted(true);
        config.set128BitKey(true);
        config.setOwnerPassword(ownerPassword);
        config.setPermissions(PdfPermissionsEnum.PRINTING.getPdfPermission()
                | PdfPermissionsEnum.COPY.getPdfPermission());
        return config;
    }

    /** PDF/A-2u: fonts embedded by the template, an sRGB output intent from the ICC profile file. */
    public static SimplePdfExporterConfiguration archival(Path srgbProfile) {
        SimplePdfExporterConfiguration config = new SimplePdfExporterConfiguration();
        config.setMetadataTitle("Invoice summary");
        config.setPdfaConformance(PdfaConformanceEnum.PDFA_2U);
        config.setIccProfilePath(srgbProfile.toString());
        return config;
    }

    /** CMYK colours through a CMYK ICC profile, no print scaling and a print dialog on open. */
    public static SimplePdfExporterConfiguration forPrint(Path cmykProfile) {
        SimplePdfExporterConfiguration config = new SimplePdfExporterConfiguration();
        config.setUseCMYKColors(true);
        config.setIccProfilePath(cmykProfile.toString());
        config.setPrintScaling(PdfPrintScalingEnum.NONE);
        config.setPdfJavaScript("this.print({bUI: true, bSilent: false, bShrinkToFit: false});");
        return config;
    }
}

A small main class runs all four. The owner password comes from an environment variable, and the two ICC profile file paths from the command line, because a profile file is a binary asset that this page does not ship.

package billing;

import java.math.BigDecimal;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.List;

import net.sf.jasperreports.engine.JasperPrint;
import net.sf.jasperreports.pdf.SimplePdfReportConfiguration;

public class ExportDemo {

    public static void main(String[] args) throws Exception {
        Path srgb = Path.of(args[0]);    // an sRGB ICC profile file, for example sRGB_v4_ICC_preference.icc
        Path cmyk = Path.of(args[1]);    // a CMYK ICC profile file
        String ownerPassword = System.getenv("BILLING_PDF_OWNER_PASSWORD");

        InvoicePdfExporter exporter = new InvoicePdfExporter();
        JasperPrint print = exporter.fill("Ada Lovelace", List.of(
                new InvoiceLine("Pride and Prejudice", 2, new BigDecimal("19.90")),
                new InvoiceLine("Don Quixote", 1, new BigDecimal("24.50"))));

        SimplePdfReportConfiguration reportConfig = new SimplePdfReportConfiguration();
        reportConfig.setBookmarksEnabled(true);

        Files.write(Path.of("invoice-standard.pdf"), exporter.export(print, PdfProfiles.standard(), reportConfig));
        Files.write(Path.of("invoice-restricted.pdf"), exporter.export(print, PdfProfiles.restricted(ownerPassword), null));
        Files.write(Path.of("invoice-archival.pdf"), exporter.export(print, PdfProfiles.archival(srgb), null));
        Files.write(Path.of("invoice-print.pdf"), exporter.export(print, PdfProfiles.forPrint(cmyk), null));
    }
}

Every profile above produced a file that pypdf could open, with the title, producer and PDF header the profile asked for. The sections below say what was observed for each setting.

Metadata, version and compression

SimplePdfExporterConfiguration exposes the document information dictionary: setMetadataTitle, setMetadataAuthor, setMetadataSubject, setMetadataKeywords, setMetadataCreator and setMetadataProducer, plus setDisplayMetadataTitle(true), which makes the viewer show the title in its title bar instead of the file name. In the run, all six values appeared in the PDF, /DisplayDocTitle was set in the viewer preferences, and the producer string changed from the default OpenPDF 1.3.43.jaspersoft.2 to the one given. Without a creator, the file records JasperReports Library version 7.0.8-2da2e11c as the creating application.

setPdfVersion takes a PdfVersionEnum from VERSION_1_2 to VERSION_1_7. The run with VERSION_1_7 produced a file whose header is %PDF-1.7; without a version the default export produced %PDF-1.5. The exporter does not offer PDF 2.0. For what the versions allow, see PDF Standards & Conformance.

setCompressed(true) is about the file structure, not the page content. Page content streams were Flate-compressed in the run even with compressed at its default of false. With true, the file also contained an object stream and a cross-reference stream (the structures described in PDF File Structure), which saved about one kilobyte on a one-page invoice, and those structures need PDF 1.5 or later.

Bookmarks (the outline in the viewer’s side panel) are a per-report setting: SimplePdfReportConfiguration.setBookmarksEnabled defaults to true, and bookmarks come from the template’s bookmark anchors, covered in Hyperlinks, Bookmarks & Books. In batch mode, setCreatingBatchModeBookmarks(true) adds one top-level outline entry per exported report. The run exported the same print twice through SimpleExporterInput.getInstance(List.of(print, print)) and got two outline entries named after the report.

Derived from the net.sf.jasperreports.export.pdf.compressed and net.sf.jasperreports.export.pdf.version entries of the configuration reference.

Encryption and permissions

Encryption has five settings: setEncrypted(true), set128BitKey(true), setUserPassword, setOwnerPassword and setPermissions. The official sample describes the model. The user password is needed to open the document, the owner password gives full access including changing the passwords and permissions, and the permissions limit what a user who opened the file can do. By default the exporter uses a 40-bit key, and set128BitKey(true) switches to 128 bits.

The permissions are an integer, combined with bitwise OR. The constants are the ones of the OpenPDF writer class, and PdfPermissionsEnum wraps them: ALL, PRINTING, MODIFY_CONTENTS, COPY, MODIFY_ANNOTATIONS, FILL_IN, SCREENREADERS, ASSEMBLY and DEGRADED_PRINTING, each with getPdfPermission(). The restricted profile above allows printing and copying and nothing else, and leaves the user password unset so that anyone can open the file.

What the run showed:

  • Without Bouncy Castle on the class path, the first encrypted export failed with a NoClassDefFoundError for org.bouncycastle.asn1.ASN1Encodable. jasperreports-pdf does not pull Bouncy Castle in, so add bcprov-jdk18on as in the dependency listing above, or the encrypted export fails at run time and the unencrypted ones do not.

  • With the 128-bit key, the file used the standard security handler with /V 2, /R 3 and /Length 128, and the permission value was -1836, printing and copying only. pypdf opened it with an empty user password and the owner password both. That is the RC4 128-bit scheme of PDF 1.4. This exporter has no setting for AES; for AES-256 see Encryption & Permissions with iText.

  • The setAllowedPermissionsHint string form (PRINTING and COPY joined with a vertical bar) gave the same permission value as the integer. setDeniedPermissionsHint with PRINTING did not give the expected "everything except printing": the file ended up with a permission value of -64, which allows nothing. If you want a specific set, state it with setPermissions or the allowed hint rather than with the denied one.

Permissions are advisory. A viewer that honours them hides the print button, but the content is in the file and tools that ignore the flags can copy it. Do not use them to protect information that must stay secret, and never hard-code the owner password: read it from the environment or a secrets manager, as the demo does.

Derived from the PDF Encrypt sample.

PDF JavaScript and print scaling

setPdfJavaScript takes a JavaScript source string that the exporter stores in the document’s JavaScript name tree. It is the classic way to make a PDF open the print dialog, as the forPrint profile does. The run confirmed that a /JavaScript entry was added to the catalog name tree. Many viewers and corporate policies disable PDF JavaScript, so do not make the document depend on it, and note that PDF/A forbids it.

setPrintScaling takes PdfPrintScalingEnum.DEFAULT or PdfPrintScalingEnum.NONE. It writes the viewer’s print-scaling preference, so that a document printed on a pre-printed form or in a print shop is not shrunk to fit. The run wrote /PrintScaling /None in the viewer preferences for NONE and /AppDefault otherwise.

CMYK output

By default the exporter writes RGB colours. setUseCMYKColors(true) converts them to CMYK, and it needs an ICC profile file: setIccProfilePath must point at a CMYK profile, because the conversion is done through it. The property is available since 6.20.5. In the run, the black text of the template was written with the four-component fill operator k and values of about 0.73, 0.70, 0.64 and 0.87, which is a rich black produced by the profile and not pure 100 percent K. That is what the profile defines, and for small text a print shop usually prefers a pure black, so check the result with the printer before committing to a profile. The profile used in the run was a generic CMYK profile that ships with macOS; use the one your print shop provides.

PDF/A archival output

setPdfaConformance takes a PdfaConformanceEnum: NONE (the default), PDFA_1A, PDFA_1B, PDFA_2A, PDFA_2B, PDFA_2U, PDFA_3A, PDFA_3B and PDFA_3U. The matching property values are pdfa1a to pdfa3u and none. The property description in the configuration reference still lists only pdfa1a, pdfa1b and none, and the PDF/A sample page lists all of the above, which is what the enum in 7.0.8 contains.

PDF/A is a set of restrictions: fonts must be embedded, colours must be device-independent, JavaScript and encryption are not allowed, and metadata must be standard (XMP). The exporter takes care of the XMP packet and of the output intent. It cannot make a template conform, so the template and the configuration have to cooperate. The run found the three places where this goes wrong:

  • Fonts. With the default font, Helvetica, the export with PDFA_1B failed with PdfXConformanceException, whose message says that all the fonts must be embedded and names Helvetica-Bold as one that is not. The default of net.sf.jasperreports.default.pdf.embedded is false. The template above avoids this by naming DejaVu Sans, a font extension from jasperreports-fonts that is embedded; with it, PDFA_1B and PDFA_2U exported without errors. The Fonts & Internationalization page covers font extensions in detail.

  • The ICC profile. Without setIccProfilePath, the export failed with JRPdfaIccProfileNotFoundException, whose message says that the ICC profile is not available to the JVM. Pass the path to an sRGB profile file, as the archival profile does. The ICC publishes sRGB profiles, and the licence of whichever file you pick decides whether you may redistribute it with your application. setEmbedIccProfile controls whether the profile is embedded, and the official sample states that PDF/A compliance requires embedding it, which inhibits that setting.

  • The "a" levels. PDFA_1A, PDFA_2A and PDFA_3A require a tagged logical structure. The run with PDFA_1A and setTagged(false) completed without an error and wrote XMP that claims conformance level A, but the file had no structure tree. It cannot be a conforming PDF/A-1a file. The "a" levels need the tagged-PDF support described in the next section, so with the Community edition use a "b" or "u" level.

What the exporter wrote for PDFA_1B and PDFA_2U: an /OutputIntents entry with the sRGB identifier, an XMP stream with pdfaid:part and pdfaid:conformance matching the level, and the header %PDF-1.4 for part 1 and %PDF-1.5 for part 2. PDFA_3B and PDFA_3U exported without an error and wrote part 3 into the XMP. Whether the files pass a validator is a separate question: run veraPDF over every document class you ship, as described in PDF/A & PDF/UA with iText.

Tagged PDF and PDF/UA

A tagged PDF carries a structure tree: headings, paragraphs, lists, tables and figure alternate text, in reading order. Screen readers and PDF/UA depend on it. JasperReports supports it through setTagged(true) and setTagLanguage, and the accessible sample describes how a template marks its content: an element property net.sf.jasperreports.export.accessibility.tag with a value from h1 to h6 for headings, the table and crosstab components that generate their own table structure, styled-text lists, and the hyperlink tooltip expression as the alternate text of an image. The same sample says that, by default, the library does not produce accessible HTML or PDF.

The catch is in 7.0.8. The property net.sf.jasperreports.export.pdf.tagger.factory, new in 7.0.8, names the factory that creates the tagger, the component that inserts the tags. Its default is DefaultPdfTaggerFactory, and the configuration reference says what that does: it raises an error if the report is required to produce tagged PDF. To avoid the error one can switch to SilentPdfTaggerFactory, which produces documents without tags, and for real tagged output the reference points to JasperReports Library Professional. So in the Community edition, tagged PDF is not available in 7.0.8.

The run confirmed both halves:

package billing;

import java.io.ByteArrayOutputStream;
import java.math.BigDecimal;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.List;

import net.sf.jasperreports.engine.DefaultJasperReportsContext;
import net.sf.jasperreports.engine.JasperPrint;
import net.sf.jasperreports.engine.SimpleJasperReportsContext;
import net.sf.jasperreports.export.SimpleExporterInput;
import net.sf.jasperreports.export.SimpleOutputStreamExporterOutput;
import net.sf.jasperreports.pdf.JRPdfExporter;
import net.sf.jasperreports.pdf.SimplePdfExporterConfiguration;
import net.sf.jasperreports.pdf.common.SilentPdfTaggerFactory;

public class TaggedExportCheck {

    public static void main(String[] args) throws Exception {
        JasperPrint print = new InvoicePdfExporter().fill("Ada Lovelace",
                List.of(new InvoiceLine("Don Quixote", 1, new BigDecimal("24.50"))));

        SimplePdfExporterConfiguration config = new SimplePdfExporterConfiguration();
        config.setTagged(true);
        config.setTagLanguage("en-US");

        // 1. Default tagger factory: the export fails with a JRRuntimeException.
        try {
            export(new JRPdfExporter(), print, config);
        } catch (RuntimeException e) {
            System.out.println("default factory: " + e.getMessage());
        }

        // 2. Silent tagger factory: the export succeeds and the file has no real structure.
        SimpleJasperReportsContext context = new SimpleJasperReportsContext(DefaultJasperReportsContext.getInstance());
        context.setProperty(JRPdfExporter.PDF_TAGGER_FACTORY_PROPERTY, SilentPdfTaggerFactory.class.getName());
        Files.write(Path.of("invoice-silent.pdf"), export(new JRPdfExporter(context), print, config));
    }

    private static byte[] export(JRPdfExporter exporter, JasperPrint print, SimplePdfExporterConfiguration config)
            throws Exception {
        ByteArrayOutputStream out = new ByteArrayOutputStream();
        exporter.setExporterInput(new SimpleExporterInput(print));
        exporter.setExporterOutput(new SimpleOutputStreamExporterOutput(out));
        exporter.setConfiguration(config);
        exporter.exportReport();
        return out.toByteArray();
    }
}
  • With the default factory, setTagged(true) failed with JRRuntimeException whose message says that tagged PDF export is not supported, followed by the URL of the property.

  • With SilentPdfTaggerFactory set on a SimpleJasperReportsContext, the export succeeded, and the file had /Lang, /MarkInfo with /Marked true and a /StructTreeRoot. The structure tree root was empty and the page content had no marked-content operators. So the file announces itself as tagged and contains no structure.

The second result is the risk. A file like that passes a quick "is it tagged" look, and it is not accessible. Do not set the silent factory in a service that promises accessible documents. Either use the Professional edition, whose tagger this page did not run, or produce the accessible document with a tool that supports it, as in PDF/A & PDF/UA with iText. The accessibility side of the templates, whichever tagger is used, is the same as for HTML, see Web Accessibility.

The OpenPDF producer

The old books describe a JasperReports that wrote PDF through iText 1.x, later 2.1.7. That has not been the case for a long time: the exporter writes through the PDF producer that the property net.sf.jasperreports.export.pdf.producer.factory names, ClassicPdfProducerFactory by default, and that producer is built on OpenPDF. OpenPDF is the LGPL and MPL community fork of the old iText API, so JasperReports 7.0.8 stays free of the AGPL that current iText versions carry. The artifact is a Jaspersoft build, 1.3.43.jaspersoft.2, with the original com.lowagie.text package names. The OpenPDF 3.0 line renames them to org.openpdf, but JasperReports 7.0.8 does not use it, so a project that also depends on OpenPDF 3.x for other reasons must keep the two apart.

Consequences worth knowing:

  • The default producer string in the PDF is OpenPDF 1.3.43.jaspersoft.2, visible in every file of the run that did not set setMetadataProducer.

  • The permissions constants and several of the error messages above (PdfXConformanceException) come from OpenPDF.

  • Encryption is RC4 only because of the exporter configuration, not because of OpenPDF: the bundled OpenPDF build has AES constants (ENCRYPTION_AES_128, ENCRYPTION_AES_256_V3 in PdfWriter), but the exporter only offers the 40-bit/128-bit RC4 switch. Tagged PDF is a separate, Professional component because the 7.0.8 change log moved the complete tagging implementation out of the Community Edition.

  • The producer is replaceable in principle, through a PdfProducerFactory, but that is an extension point and not a supported configuration. This page did not try it.

The same facts are the reason the signing pages of this section post-process the exported bytes: JasperReports has no native signing, and the PDF it writes is an input to a signing tool.

Settings at a glance

Setting Property Default

setMetadataTitle, Author, Subject, Keywords, Creator, Producer

net.sf.jasperreports.export.pdf.metadata.*

none; producer is OpenPDF

setDisplayMetadataTitle

net.sf.jasperreports.export.pdf.display.metadata.title

none

setPdfVersion

net.sf.jasperreports.export.pdf.version

none (a PDF 1.5 header in the run)

setCompressed

net.sf.jasperreports.export.pdf.compressed

false

setEncrypted, set128BitKey

net.sf.jasperreports.export.pdf.encrypted, net.sf.jasperreports.export.pdf.128.bit.key

false, false

setUserPassword, setOwnerPassword

net.sf.jasperreports.export.pdf.user.password, net.sf.jasperreports.export.pdf.owner.password

none

setPermissions, allowed and denied hints

net.sf.jasperreports.export.pdf.permissions.allowed, net.sf.jasperreports.export.pdf.permissions.denied

none

setPdfJavaScript

net.sf.jasperreports.export.pdf.javascript

none

setPrintScaling

net.sf.jasperreports.export.pdf.print.scaling

default

setUseCMYKColors

net.sf.jasperreports.export.pdf.use.cmyk.colors

false

setPdfaConformance

net.sf.jasperreports.export.pdfa.conformance

none

setIccProfilePath, setEmbedIccProfile

net.sf.jasperreports.export.pdfa.icc.profile.path, net.sf.jasperreports.export.pdfa.embed.icc.profile

none, false

setTagged, setTagLanguage

net.sf.jasperreports.export.pdf.tagged, net.sf.jasperreports.export.pdf.tag.language

false, none

setCreatingBatchModeBookmarks

net.sf.jasperreports.export.pdf.create.batch.mode.bookmarks

false

SimplePdfReportConfiguration.setBookmarksEnabled

net.sf.jasperreports.export.pdf.bookmarks.enabled

true

The defaults come from the default.jasperreports.properties file inside the 7.0.8 artifacts, which is the file to read when a documented default and the behavior disagree.