Blog About Contact

Simple Guide To Sub-reports in JasperReports / iReport

Published Mon, 8 Feb 2010 • 73 comments

Reporting tools... why is it so hard??

It seems like it's practically a requirement that all business reporting tools be difficult to learn, use and work with. JasperReports / iReport is no different. Don't get me wrong it's a good solution to a certain kind of problem (and it's free and open source which doesn't hurt), but the iReport UI is (and I'll try to be kind) utilitarian at best.

Anyhow, to do any non-trivial report with iReport, you'll probably need to use sub-reports, which are not very clearly documented as to how the hell they work.

I'll be talking strictly about Java-bean collection data sources for this article, so you will need to translate if you are doing direct SQL queries or something else.

The Java-bean data source is nice because we can utilise our existing domain layer in order to create our report, rather than replicating the same logistics in SQL (or worse - stored procedures) and thereby having two places to update when requirements change.

To easily understand how sub-reports work, we need to understand how JasperReports works in general.

JasperReports are stored in XML files (JRXML) - if you've created any report you'd be familiar with these files. These XML files are translated to Java source code by JasperReports, and then compiled into regular Java CLASS files, which are executable by the Jasper engine. You could say that Jasper compiles the JRXML files into Java, and then Java will compile these into byte code.

When you execute a report you are essentially executing Java code. Keep this in mind when filling out expression fields in your report, and things will start to make more sense.

Generally you use a sub-report in a situation where you have a two or more child lists of data relating to a single parent element. A common use case would be a report with multiple details bands of different types. I say "different types", because if you have nested children that relate to the same data set, then generally you can achieve formatting using groups with breaking on certain fields in the data set. Things get complicated though where you have one big report, which has multiple unrelated data sets inside it.

To avoid getting too meta - here's a concrete example...

This report has a single main detail band (contact details - first and last names) and two sub-detail bands for each contact - addresses and phone numbers.

In Jasper we need to use two sub-reports to implement this. But before we get too far, let's look at how we would create Javabeans to store this data.

The addresses:

package com.visural.report;

public class AddressBean {

    private String type;
    private String address;

    public String getAddress() {
        return address;
    }

    public void setAddress(String address) {
        this.address = address;
    }

    public String getType() {
        return type;
    }

    public void setType(String type) {
        this.type = type;
    }
}

The phone numbers:

package com.visural.report;

public class PhoneBean {

    private String type;
    private String number;

    public String getNumber() {
        return number;
    }

    public void setNumber(String number) {
        this.number = number;
    }

    public String getType() {
        return type;
    }

    public void setType(String type) {
        this.type = type;
    }
}

And the contact (parent) bean:

package com.visural.report;

import java.util.List;

public class ContactBean {

    private String firstName;
    private String lastName;
    private List<AddressBean> addresses;
    private List<PhoneBean> phones;

    public List<AddressBean> getAddresses() {
        return addresses;
    }

    public void setAddresses(List<AddressBean> addresses) {
        this.addresses = addresses;
    }

    public String getFirstName() {
        return firstName;
    }

    public void setFirstName(String firstName) {
        this.firstName = firstName;
    }

    public String getLastName() {
        return lastName;
    }

    public void setLastName(String lastName) {
        this.lastName = lastName;
    }

    public List<PhoneBean> getPhones() {
        return phones;
    }

    public void setPhones(List<PhoneBean> phones) {
        this.phones = phones;
    }
}

Finally, we need a test data source to use in developing our report. Here's the factory for the report pictured earlier...

package com.visural.report;

import java.util.Arrays;
import java.util.List;

public class ContactFactory {

    public static List<ContactBean> create() {
        ContactBean stub = new ContactBean();
        stub.setFirstName("John");
        stub.setLastName("Smith");

        AddressBean address1 = new AddressBean();
        address1.setType("Home");
        address1.setAddress("123 Fake St\nFaketown\nFK 12345");
        AddressBean address2 = new AddressBean();
        address2.setType("Work");
        address2.setAddress("321 Bogus St\nFaketown\nFK 12345");
        stub.setAddresses(Arrays.asList(address1, address2));

        PhoneBean phone1 = new PhoneBean();
        phone1.setType("Home");
        phone1.setNumber("03 9876 1234");
        PhoneBean phone2 = new PhoneBean();
        phone2.setType("Work");
        phone2.setNumber("03 1234 9876");
        PhoneBean phone3 = new PhoneBean();
        phone3.setType("Mobile");
        phone3.setNumber("0432 123 456");
        stub.setPhones(Arrays.asList(phone1, phone2, phone3));

        return Arrays.asList(stub);
    }
}

The thing to notice here is that we have modelled the Javabeans exactly as we would if they were being used for some other purpose, i.e. we haven't done anything special to make them usable in JasperReports / iReport.

In Jasper we configure a Javabeans data source that is created from a Collection, using our ContactFactory.create() method. We then add fields as follows, and create our report layout:

Note that the field names correspond to the Javabean property names.

Now for the important part. We then need to set the data source on the sub-report's properties:

So we can just use an "data source expression" as our connection for the sub-report, and for the expression we create a data source on the fly using our collection of java beans from our current data source's "addresses" and "phones" fields. "new net.sf.jasperreports.engine.data.JRBeanCollectionDataSource(datasource.getPhones())" is the Java code we would write if we were to create a Javabean data source in our regular Java code. JasperReports will replace $F{phones} with "datasource.getPhones()" (or its equivalent) as part of it's JRXML to Java compilation process.

The rest is simple; our sub-reports define fields that match the AddressBean and PhoneBean properties:

With all that in mind, it really is a case of "it's simple when you know how", but be that as it may, I couldn't find a concise description of how to do this in my googling. Hopefully this post will fill that gap. Perhaps everyone is still writing SQL-based reports against a single database? :) I don't know, but using Javabeans is a nice flexible alternative to use SQL-based reports that gives you a lot of options in the long term.

If you're still confused you can download the above code, and the JRXML reports in a Netbeans 6 project here.

Also, try one of these books. Jasper & iReport's documentation is pretty thin online, so having a reference book can be handy -


About the Author

Richard Nichols is an Australian software engineer with a passion for making things.

Follow him on twitter or subscribe by RSS or email.

You might also enjoy reading -


Discuss / Comment

There are 73 comments.

Add a comment

  • {{e.error}}

Thanks for your comment!/

Required.
Valid email address required.
Required.
Posting message, please wait...