The PDF format has established a strong position as a format used for printing and archiving formal documents. This is why pretty much all software developers have at some point faced a requirement to create PDF files like receipts or reports. Developers in the JVM ecosystem have a huge set of mature industry proven solutions for doing this. There are both “low level” libraries like iText and PDFBox that you can command with raw Java APIs and several template based solutions.
From template based solutions, JasperReports is one of the most commonly used tools. The actual library is OSS, but if you wish to have a good visual editor for the templates, you probably want to flash your credit card. Apache FOP is another possibility, but has pretty much the same drawback - there is no free and easy to use template editor for it.
Why not use your favourite office suite as the template editor?
An interesting approach is to use normal office documents, like ODT or DOCX files, as templates. The big advantage with this approach is that you have a “visual editor” already installed on your computer and even your mother can use it. JODReports/JODConverter is a popular tool with this approach. It uses the real OpenOffice installation behind the scenes to generate the report. The usage of OpenOffice gives you pixel perfect results, but on the other hand, your application won’t be as portable and easy to setup. Also, JODConverter currently has no official maintainer, although there are several active forks in the Github.
A pure JVM solution using XDocReport
XDocReport is a library with a similar target, but it works completely within the JVM, making it really easy for development and deployment. As it also has a liberal license, I decided to give it a try in my recent example application. As it practically replicates the rendering of ODT/DOCX files, the resulted file is not necessarily as perfect as with real OpenOffice, but nothing serious came up during my experiments.
Based on my good experiences, I wrote the following tutorial on how you can set it up to your Java web application.
Related reading that might interest you
Download our free guide to the Future of Web Apps
Preparations and dependencies
XDocReport is a well modularised project with several different configuration possibilities. It supports both ODT and DOCX templates, two different template engines (Freemarker and Velocity) and multiple target formats (docx/odt, xhtml, pdf). For the ODT-to-PDF workflow, using Freemarker template engine, I needed to add the following dependencies to my pom.xml:
<dependency> <groupId>fr.opensagres.xdocreport</groupId> <artifactId>fr.opensagres.xdocreport.document.odt</artifactId> <version>1.0.5</version> </dependency> <dependency> <groupId>fr.opensagres.xdocreport</groupId> <artifactId>fr.opensagres.xdocreport.template.freemarker</artifactId> <version>1.0.5</version> </dependency> <dependency> <groupId>fr.opensagres.xdocreport</groupId> <artifactId>fr.opensagres.xdocreport.converter.odt.odfdom</artifactId> <version>1.0.5</version> </dependency>
There are a couple of transitive dependencies that are pulled in automatically. The iText dependency defaults to an older LGPL version which is suitable for pretty much every business application (mentioned as the most recent iText versions infamously use AGPL/commercial dual licensing). Thanks to the pure JVM setup, nothing else is required to get started.
If you want to have some other kind of setup, like use MS Word documents as templates, I suggest to look at one of the quality examples. XDocReport has lots of quality examples with various setups.
Creating the template
As expected due to the nature of the approach, creating and especially customising the templates is easy. You can use familiar formatting controls and the fonts you want (just make sure they are installed on the server as well) or you can copy-paste decorative vector or raster graphics directly to your template with LibreOffice/OpenOffice/MS Word and the graphics are automatically added to the generated reports. It is really fast to draft the template or to just replace the dummy content from your specification document with property references (example with Freemarker: ${sender.email} ).
But there are a couple of gotchas. First of, using template libraries may be tricky in the beginning. You don’t need to be a developer to design the templates, but those creating them should learn at least some basic tricks related to the used template library. I’d at least instruct the users building the templates to use exclamation mark at the end of each parameter, like “${to.email!}”. That adds basic null checks to your variables. I have to confess that this was the part where I lost most of my time when setting up the example. :-) But once you have a base template set up, pretty much any user with basic “office skills” can do modifications to the existing template.
I also suggest to use rather short parameter names. For example, in the example for invoice row, I initially used parameters like “${invoiceRow.total}”. But in the pretty tight table presentation I used for invoice rows, the length of the variable was quite a lot wider than the actual content to be used, making it really hard to figure out the actual output while designing the template in LibreOffice. Finally I flattened the variable name to “${r.total}”.
One drawback, at least in using the ODT template, was that the tab stops don’t work properly. As a workaround, you have to use a table based layout in your template. For me that was just fine, once I figured out what was wrong.
For LibreOffice there is also a separate plugin that should help designing the template, but I managed to do my exercise without it.
In some applications the template(s) might be quite “static”. In this case it is fine to just store the template as a resource as I did for my default template. But to really take the advantage of this library, you probably want to allow your end users to download the default or existing template for customisation, and to upload the updated ones back to the system. In the example application the customised versions are saved into a @Lob field in the Invoicer entity. Check out the InvoicerForm and the TemplateField in the demo application for how you can do this.
Passing the generated file to the end user
Creating the actual report file can be split to four steps:
-
Creating an IXDocReport report instance from the raw template
-
Defining the Options, which instruct XDocReport with what it should do
-
Defining the IContext, which practically contains the parameters used to construct the report
-
Igniting the report file generation
To get a reference to IXDocReport, you should use a helper method from XDocReportRegistry. As parameters you’ll provide an input stream to the raw template and the type of template engine in use. In the example application we use either the default template or a custom one specified by the end user:
// Get template stream (either the default or overridden by the user) InputStream in = getTemplate(invoice.getInvoicer()); // Prepare the IXDocReport instance based on the template, using // Freemarker template engine IXDocReport report = XDocReportRegistry.getRegistry(). loadReport(in, TemplateEngineKind.Freemarker);
The Options just give some hints for XDocReports to what to finally output from our template. In the example, we aim for a PDF file from an ODF based template file, so the correct configuration is accomplished like this:
Options options = Options.getTo(ConverterTypeTo.PDF).via(ConverterTypeVia.ODFDOM);
Before the actual generation of the PDF file, we still need to define the “context” for the report generation, which is pretty much declaring the properties available for the template. As the properties in the example application are read from JPA entities, and there are some lazy loaded fields, I placed the whole PDF generation into my facade (EJB). This way I can get an attached JPA entity that I pass to XDocReports and the lazy relations are fetched automatically during the PDF generation.
XDocReports (or actually the template library in use) would be smart enough to support “nested properties” with dot notation, but I also “flattened” some properties to keep the template simpler for the end users to customise.
// Add properties to the context IContext ctx = report.createContext(); ctx.put("invoice", invoice); ctx.put("to", invoice.getTo()); ctx.put("sender", invoice.getInvoicer()); // instruct XDocReport to inspect InvoiceRow entity as well // which is given as a list and iterated in a table FieldsMetadata metadata = report.createFieldsMetadata(); metadata.load("r", InvoiceRow.class, true); ctx.put("r", invoice.getInvoiceRows());
The invoice.getInvoiceRows() returns a list of InvoiceRow instances. To make the iteration in the template work flawlessly, XDocReport needs a small bit of extra hints as seen in the above example.
Finally, the actual content of the generated PDF file is written to a given output stream like this:
// Write the PDF file to output stream report.convert(ctx, options, out); out.close();
So to use the actual helper method, an OutputStream needs to be passed. To connect this API to our Vaadin based user interface, we need to create a “file download”, initiated from a button. With core Vaadin API’s, you commonly do this with a combination of a Button, a FileDownloader extension and a StreamResource. I find the API a bit cumbersome (especially StreamResource asking an InputStream instead of providing an OutputStream), so I have created a handy helper called DownloadButton to my Viritin add-on. With DownloadButton you can connect your UI to the facade with the following oneliner:
new DownloadButton(out -> facade.writeAsPdf(invoice, out))
To polish the solution, I also provide a sane name for the downloaded PDF file, and decorate the button with a cool PDF icon from the FontAwesome icon set and a borderless “icon only” style. The full usage in the example application is written like this:
final MButton pdfDownload = new DownloadButton( out -> facade.writeAsPdf(invoice, out)) .setFileName("invoice_" + invoice.getInvoiceNumber() + ".pdf") .withIcon(FontAwesome.FILE_PDF_O) .withStyleName(ValoTheme.BUTTON_ICON_ONLY);
The fully functional, end user customisable PDF generation is now done. A version of this example app is in use by one of my hobby projects called Jukolan Viesti, but so far I have experience of XDocReport only from this project. With that disclaimer, I can recommend checking it out if XDocReport would be your next tool for PDF generation. As it is a pure JVM library, the Vaadin integration part was naturally a no-brainer ;-)
Check out the full example app
Learn more about Vaadin
Discover the easiest way to build web apps in Java
Related reading that might interest you
Download our free guide to the Future of Web Apps