First time here? Get an overview of all topics you'll find answers for on this blog here.

Tired of text/plain ? Have a look at my online courses or YouTube channel for more Java, Spring Framework & Jakarta EE content.

Generating documents for e.g. invoices or reports is a central use case for enterprise applications. As a Java developer, you have a wide range of possible libraries to manipulate and create Word, Excel or PDF documents. To help you choose the right library, I'll demonstrate an example to generate PDF documents with Java EE. This requires Apache PDFBox and for creating charts the XChart library. The example is based on Java 11, Java EE 8 and deployed to Open Liberty.

Java EE project setup

The pom.xml looks like the following:

<project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://maven.apache.org/POM/4.0.0" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>de.rieckpil.blog</groupId> <artifactId>charts-in-pdf-java-ee</artifactId> <version>1.0-SNAPSHOT</version> <packaging>war</packaging> <properties> <maven.compiler.source>11</maven.compiler.source> <maven.compiler.target>11</maven.compiler.target> <failOnMissingWebXml>false</failOnMissingWebXml> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding> </properties> <dependencies> <dependency> <groupId>javax</groupId> <artifactId>javaee-api</artifactId> <version>8.0</version> <scope>provided</scope> </dependency> <dependency> <groupId>org.eclipse.microprofile</groupId> <artifactId>microprofile</artifactId> <version>3.2</version> <type>pom</type> <scope>provided</scope> </dependency> <dependency> <groupId>org.apache.pdfbox</groupId> <artifactId>pdfbox</artifactId> <version>2.0.20</version> </dependency> <dependency> <groupId>org.knowm.xchart</groupId> <artifactId>xchart</artifactId> <version>3.6.3</version> </dependency> </dependencies> <build> <finalName>charts-in-pdf-java-ee</finalName> </build> </project> 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 <project xmlns : xsi = "http://www.w3.org/2001/XMLSchema-instance" xmlns = "http://maven.apache.org/POM/4.0.0" xsi : schemaLocation = "http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd" > <modelVersion> 4.0.0 </modelVersion> <groupId> de.rieckpil.blog </groupId> <artifactId> charts-in-pdf-java-ee </artifactId> <version> 1.0-SNAPSHOT </version> <packaging> war </packaging> <properties> <maven.compiler.source> 11 </maven.compiler.source> <maven.compiler.target> 11 </maven.compiler.target> <failOnMissingWebXml> false </failOnMissingWebXml> <project.build.sourceEncoding> UTF-8 </project.build.sourceEncoding> <project.reporting.outputEncoding> UTF-8 </project.reporting.outputEncoding> </properties> <dependencies> <dependency> <groupId> javax </groupId> <artifactId> javaee-api </artifactId> <version> 8.0 </version> <scope> provided </scope> </dependency> <dependency> <groupId> org.eclipse.microprofile </groupId> <artifactId> microprofile </artifactId> <version> 3.2 </version> <type> pom </type> <scope> provided </scope> </dependency> <dependency> <groupId> org.apache.pdfbox </groupId> <artifactId> pdfbox </artifactId> <version> 2.0.20 </version> </dependency> <dependency> <groupId> org.knowm.xchart </groupId> <artifactId> xchart </artifactId> <version> 3.6.3 </version> </dependency> </dependencies> <build> <finalName> charts-in-pdf-java-ee </finalName> </build> </project>

I've chosen Apache PDFBox (GitHub) as the PDF library as the library has active maintainer, is open–source, easy-to-learn and good enough for basic use cases. The charting library XChart (GitHub) is a light-weight Java library for plotting data with an intuitive developer API, is providing really good example charts and capable of plotting every important chart type (XYChart, Bar-, Pie-, Histogram-, Dial, Radar, Stick Chart …).

For this simple showcase, the additional dependencies are packed within the .war but they could and should be part of the application server to have a thin war with quick deployment cycles.

Creating a JAX-RS endpoint to download the PDF

The JAX-RS configuration for this application is quite simple:

@ApplicationPath("resources") public class JAXRSConfiguration extends Application { } 1 2 3 4 @ApplicationPath ( "resources" ) public class JAXRSConfiguration extends Application { }

For downloading the generated PDF document I've added the following JAX-RS endpoint:

@Path("reports") @Stateless public class ReportResource { @Inject private PdfGenerator pdfGenerator; @GET @Produces(MediaType.APPLICATION_OCTET_STREAM) public Response createSimplePdfWithChart() throws IOException { return Response.ok(pdfGenerator.createPdf(), MediaType.APPLICATION_OCTET_STREAM) .header("Content-Disposition", "attachment; filename=\"simplePdf.pdf\"").build(); } } 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 @Path ( "reports" ) @Stateless public class ReportResource { @Inject private PdfGenerator pdfGenerator ; @GET @Produces ( MediaType . APPLICATION_OCTET_STREAM ) public Response createSimplePdfWithChart ( ) throws IOException { return Response . ok ( pdfGenerator . createPdf ( ) , MediaType . APPLICATION_OCTET_STREAM ) . header ( "Content-Disposition" , "attachment; filename=\"simplePdf.pdf\"" ) . build ( ) ; } }

To notify the client about the content type of the response the JAX-RS annotation @Produces(MediaType.APPLICATION_OCTET_STREAM) is required. In addition, I've added the Content-Disposition header so that regular browsers will directly download the incoming file with the given filename simplePdf.pdf .

The injected EJB PdfGenerator is responsible for generating the PDF document as a byte array:

@Stateless public class PdfGenerator { public byte[] createPdf() throws IOException { try (PDDocument document = new PDDocument()) { PDPage page = new PDPage(PDRectangle.A4); page.setRotation(90); float pageWidth = page.getMediaBox().getWidth(); float pageHeight = page.getMediaBox().getHeight(); PDPageContentStream contentStream = new PDPageContentStream(document, page); PDImageXObject chartImage = JPEGFactory.createFromImage(document, createChart((int) pageHeight, (int) pageWidth)); contentStream.transform(new Matrix(0, 1, -1, 0, pageWidth, 0)); contentStream.drawImage(chartImage, 0, 0); contentStream.close(); document.addPage(page); ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream(); document.save(byteArrayOutputStream); return byteArrayOutputStream.toByteArray(); } } // ... } 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 @Stateless public class PdfGenerator { public byte [ ] createPdf ( ) throws IOException { try ( PDDocument document = new PDDocument ( ) ) { PDPage page = new PDPage ( PDRectangle . A4 ) ; page . setRotation ( 90 ) ; float pageWidth = page . getMediaBox ( ) . getWidth ( ) ; float pageHeight = page . getMediaBox ( ) . getHeight ( ) ; PDPageContentStream contentStream = new PDPageContentStream ( document , page ) ; PDImageXObject chartImage = JPEGFactory . createFromImage ( document , createChart ( ( int ) pageHeight , ( int ) pageWidth ) ) ; contentStream . transform ( new Matrix ( 0 , 1 , - 1 , 0 , pageWidth , 0 ) ) ; contentStream . drawImage ( chartImage , 0 , 0 ) ; contentStream . close ( ) ; document . addPage ( page ) ; ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream ( ) ; document . save ( byteArrayOutputStream ) ; return byteArrayOutputStream . toByteArray ( ) ; } } // ... }

PDDocument is the central class for creating new PDF documents with Apache PDFBox. In this example, I'm using a try-with-resources block to create a new document and close it afterward. The PDDocument object can contain several PDPage objects which represent a physical PDF page. To display the chart later on in landscape mode, we have to rotate the page for 90 degrees. Furthermore, writing content to the PDPage requires opening a PDPageContentStream .

Adding a chart to the PDF document

For including the chart as an image to the PDF page, I'm creating a PDImageXObject from a BufferedImage , transforming it (to also rotate the image) and drawing it to the page at position x=0 and y=0 (starting bottom left corner).

The chart is created as the following:

private BufferedImage createChart(int width, int height) { XYChart chart = new XYChartBuilder().xAxisTitle("X").yAxisTitle("Y").width(width).height(height) .theme(ChartTheme.Matlab).build(); XYSeries series = chart.addSeries("Random", null, getRandomNumbers(200)); series.setMarker(SeriesMarkers.NONE); return BitmapEncoder.getBufferedImage(chart); } private double[] getRandomNumbers(int numPoints) { double[] y = new double[numPoints]; for (int i = 0; i < y.length; i++) { y[i] = ThreadLocalRandom.current().nextDouble(0, 1000); } return y; } 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 private BufferedImage createChart ( int width , int height ) { XYChart chart = new XYChartBuilder ( ) . xAxisTitle ( "X" ) . yAxisTitle ( "Y" ) . width ( width ) . height ( height ) . theme ( ChartTheme . Matlab ) . build ( ) ; XYSeries series = chart . addSeries ( "Random" , null , getRandomNumbers ( 200 ) ) ; series . setMarker ( SeriesMarkers . NONE ) ; return BitmapEncoder . getBufferedImage ( chart ) ; } private double [ ] getRandomNumbers ( int numPoints ) { double [ ] y = new double [ numPoints ] ; for ( int i = 0 ; i < y . length ; i ++ ) { y [ i ] = ThreadLocalRandom . current ( ) . nextDouble ( 0 , 1000 ) ; } return y ; }

I'm using a simple XYChart with randomly generated numbers and creating it as high and wide as the underlying PDF page is:

The resulting PDF document looks like this: simplePdf.pdf

There are quite a lot more possibilities with Apache PDFBox and XChart as the plotting engine, but this example should give you a quick start to get in touch with these libraries.

You can find the whole codebase to generate a PDF document with Java EE including instructions on how to run it on your machine on GitHub.

Have fun generating PDF documents with Java EE,

Phil