In this post we’ll see how to generate a PDF in Spring MVC using the fields from a view page (JSP).
Technologies used
Following is the list of tools used for the Spring MVC PDF generation example.
- Spring 5.0.8 Release (Spring core, spring web, spring webmvc).
- Java 10
- Tomcat server V 9.0.10
- Eclipse Photon 4.8.0 for Java EE development
- iText 7.1.3
- OpenPDF 1.2.3
- Options for generating PDF in Spring MVC
- Spring MVC PDF generation example using iText
- Spring MVC PDF generation example flow
- Spring MVC PDF generation – iText related classes
- Spring MVC PDF generation – Controller Class
- Spring MVC PDF generation – Views
- Spring MVC PDF generation – Model Classes
- Spring MVC PDF generation – Configuration
- Spring MVC PDF generation using OpenPDF
- PDF view class for OpenPDF
- Properties class change for OpenPDF
- Deploying and testing the application
- Generated PDF - Spring MVC PDF Generation Example
Options for generating PDF in Spring MVC
- One of the option for generating PDF is iText. In Spring framework iText support is for a very old version so requires some work on your behalf to get it working with the current versions.
- Another option is OpenPDF which is a fork from iText. Spring framework class AbstractPdfView can directly
be used to generate PDF using OpenPDF. Spring framework recommends OpenPDF.
Reference- https://docs.spring.io/spring/docs/current/javadoc-api/org/springframework/web/servlet/view/document/AbstractPdfView.html
Spring MVC PDF generation example using iText
First we’ll see how to generate a PDF using iText.
Maven dependenciesMaven is used for managing dependencies in this Spring MVC PDF generation example.
- Please refer Spring Web MVC Example With Annotations to see how to set Spring MVC project using Maven.
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 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>spring-mvc</groupId> <artifactId>spring-mvc</artifactId> <version>0.0.1-SNAPSHOT</version> <packaging>war</packaging> <name>Spring MVC</name> <description>Spring MVC example</description> <properties> <spring.version>5.0.8.RELEASE</spring.version> </properties> <dependencies> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-core</artifactId> <version>${spring.version}</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-context</artifactId> <version>${spring.version}</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-webmvc</artifactId> <version>${spring.version}</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-web</artifactId> <version>${spring.version}</version> </dependency> <dependency> <groupId>javax.servlet</groupId> <artifactId>javax.servlet-api</artifactId> <version>4.0.0</version> <scope>provided</scope> </dependency> <!-- For JSTL tags --> <dependency> <groupId>jstl</groupId> <artifactId>jstl</artifactId> <version>1.2</version> </dependency> <!-- For iText --> <dependency> <groupId>com.itextpdf</groupId> <artifactId>kernel</artifactId> <version>7.1.3</version> </dependency> <dependency> <groupId>com.itextpdf</groupId> <artifactId>layout</artifactId> <version>7.1.3</version> </dependency> </dependencies> <build> <sourceDirectory>src</sourceDirectory> <plugins> <plugin> <artifactId>maven-compiler-plugin</artifactId> <version>3.7.0</version> <configuration> <release>10</release> </configuration> </plugin> <plugin> <artifactId>maven-war-plugin</artifactId> <version>3.2.1</version> <configuration> <warSourceDirectory>WebContent</warSourceDirectory> </configuration> </plugin> </plugins> </build> </project>
In the pom.xml apart from Spring framework dependencies, iText dependencies are also added which adds the following jars.
kernel-7.1.3.jar io-7.1.3.jar layout-7.1.3.jar
Spring MVC PDF generation example flow
In the example there is a JSP that shows a list of Users and there is a button “ViewPDF”. In the JSP that list of users is bound to a Model.
When the button is clicked, the request is mapped to the appropriate controller method and from there the logical view name and Model where the list of users is set as attribute is transferred to the view which creates a PDF. To resolve a view to a PDF another view resolver has to be added.
Spring MVC PDF generation – iText related classes
With in Spring framework there is an abstract class AbstractPdfView which acts as a superclass for PDF views. But the AbstractPdfView class works with the original iText 2.1.7 version (with com.lowagie.* package) where as the current version is iText 7.x (with com.itextpdf package).
That requires some work on your behalf to make the Spring MVC work with the current iText version. AbstractPdfView class extends AbstractView class and adds PDF specific functionality. So, you need to do that yourself, creating a class that extends AbstractView and uses the new com.itextpdf package. You can still copy most of the functionality from the AbstractPdfView class, difference is now you are pointing to the com.itextpdf.* classes where as AbstractPdfView class uses the older com.lowagie.* classes. Also code for PDF generation in iText 7.x is a little different from iText 5.x version.
import java.io.ByteArrayOutputStream; import java.util.Map; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import org.springframework.web.servlet.view.AbstractView; import com.itextpdf.kernel.geom.PageSize; import com.itextpdf.kernel.pdf.PdfDocument; import com.itextpdf.kernel.pdf.PdfWriter; import com.itextpdf.layout.Document; public abstract class NetJSAbstractViewPDF extends AbstractView { public NetJSAbstractViewPDF() { setContentType("application/pdf"); } @Override protected boolean generatesDownloadContent() { return true; } @Override protected void renderMergedOutputModel(Map<String, Object> model, HttpServletRequest request, HttpServletResponse response) throws Exception { // IE workaround: write into byte array first. ByteArrayOutputStream baos = createTemporaryOutputStream(); // Apply preferences and build metadata. PdfWriter writer = new PdfWriter(baos); Document document = new Document(new PdfDocument(writer), PageSize.A4); buildPdfMetadata(model, document, request); // Build PDF document. buildPdfDocument(model, document, writer, request, response); document.close(); // Flush to HTTP response. writeToResponse(response, baos); } protected void buildPdfMetadata(Map<String, Object> model, Document document, HttpServletRequest request) { } protected abstract void buildPdfDocument(Map<String, Object> model, Document document, PdfWriter writer, HttpServletRequest request, HttpServletResponse response) throws Exception; }
Now NetJSAbstractViewPDF is the abstract class which provides the PDF specific functionality and you need to extend this class to implement abstract method buildPdfDocument() as per your PDF document structure requirements.
import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import org.netjs.model.User; import com.itextpdf.kernel.font.PdfFont; import com.itextpdf.kernel.font.PdfFontFactory; import com.itextpdf.kernel.pdf.PdfWriter; import com.itextpdf.layout.Document; import com.itextpdf.layout.element.Cell; import com.itextpdf.layout.element.Paragraph; import com.itextpdf.layout.element.Table; import com.itextpdf.layout.property.UnitValue; public class PDFView extends NetJSAbstractViewPDF { @Override protected void buildPdfDocument(Map<String, Object> model, Document document, PdfWriter writer, HttpServletRequest request, HttpServletResponse response) throws Exception { // List of users that will be displayed in the PDF List<User> users = (List<User>)model.get("Users"); // Create table with 3 columns of similar length Table table = new Table(new float[]{4, 4, 4}); table.setWidth(UnitValue.createPercentValue(100)); PdfFont bold = PdfFontFactory.createFont("Times-Bold"); // adding header table.addHeaderCell(new Cell().add(new Paragraph("First Name").setFont(bold))); table.addHeaderCell(new Cell().add(new Paragraph("Last Name").setFont(bold))); table.addHeaderCell(new Cell().add(new Paragraph("Email").setFont(bold))); // adding rows for(User user : users) { table.addCell(user.getFirstName()); table.addCell(user.getLastName()); table.addCell(user.getEmail()); } document.add(table); } }
Spring MVC PDF generation – Controller Class
import java.util.ArrayList; import java.util.List; import org.netjs.model.User; import org.netjs.model.UserListContainer; import org.springframework.stereotype.Controller; import org.springframework.ui.Model; import org.springframework.web.bind.annotation.ModelAttribute; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMethod; import org.springframework.web.servlet.ModelAndView; @Controller public class UserController { @RequestMapping(value = "/getUsers", method = RequestMethod.GET) public String getUsers(Model model) throws Exception{ List<User> users = getListOfUsers(); UserListContainer userList = new UserListContainer(); userList.setUsers(users); model.addAttribute("Users", userList); return "showUsers"; } @RequestMapping(value = "/viewPDF", method = RequestMethod.POST) public ModelAndView viewPDF(@ModelAttribute("Users") UserListContainer userList) throws Exception{ List<User> users = userList.getUsers(); return new ModelAndView("viewPDF", "Users", users); } // Dummy method for adding List of Users private List<User> getListOfUsers() { List<User> users = new ArrayList<User>(); users.add(new User("Jack", "Reacher", "abc@xyz.com")); users.add(new User("Remington", "Steele", "rs@cbd.com")); users.add(new User("Jonathan", "Raven", "jr@sn.com")); return users; } }
In the Controller class there are two handler methods. Method getUsers() displays the list of users in a JSP page (showUsers.jsp). In that JSP there is a “View PDF” button, the request created on clicking that button is handled by the handler method viewPDF() which passes the list of users and the logical view name to be resolved to a PDF view.
Spring MVC PDF generation – Views
showUsers.jsp
<%@ page language="java" contentType="text/html; charset=ISO-8859-1" pageEncoding="ISO-8859-1"%> <!DOCTYPE html> <%@taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c"%> <%@ taglib prefix="form" uri="http://www.springframework.org/tags/form"%> <html> <head> <meta charset="ISO-8859-1"> <title>Spring MVC List of objects display</title> </head> <body> <form:form method="POST" action="viewPDF" modelAttribute="Users"> <table> <tr> <th>First Name</th> <th>Last Name</th> <th>Email</th> </tr> <c:forEach items="${Users.users}" var="user" varStatus="tagStatus"> <tr> <td><form:input path="users[${tagStatus.index}].firstName" value="${user.firstName}" readonly="true"/></td> <td><form:input path="users[${tagStatus.index}].lastName" value="${user.lastName}" readonly="true"/></td> <td><form:input path="users[${tagStatus.index}].email" value="${user.email}" readonly="true"/></td> </tr> </c:forEach> </table> <input type="submit" value="View PDF" /> </form:form> </body> </html>
This JSP displays the users in the List by iterating the List and again bind list to the Model. Clicking “View PDF” button generates the PDF using the List bound to the Model.
We have already seen the PDF view class PDFView.java.
Spring MVC PDF generation – Model Classes
List will have objects of the User class.
public class User { private String firstName; private String lastName; private String email; public User() { } public User(String firstName, String lastName, String email) { this.firstName = firstName; this.lastName = lastName; this.email = email; } 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 String getEmail() { return email; } public void setEmail(String email) { this.email = email; } }
Following class acts a container for the List of User objects.
public class UserListContainer { private List<User> users; public List<User> getUsers() { return users; } public void setUsers(List<User> users) { this.users = users; } }
Spring MVC PDF generation – Configuration
The Spring configuration file is as follows.
mvcexample-servlet.xml
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:mvc="http://www.springframework.org/schema/mvc" xmlns:context="http://www.springframework.org/schema/context" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation=" http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd"> <mvc:annotation-driven /> <context:component-scan base-package="org.netjs.controller" /> <bean id="PDFResolver" class= "org.springframework.web.servlet.view.ResourceBundleViewResolver"> <property name="order" value="1"/> <property name="basename" value="pdf-view"/> </bean> <bean id="JSPViewResolver" class= "org.springframework.web.servlet.view.InternalResourceViewResolver"> <property name="order" value="2"/> <property name="viewClass" value="org.springframework.web.servlet.view.JstlView" /> <property name="prefix" value="/WEB-INF/jsp/" /> <property name="suffix" value=".jsp" /> </bean> </beans>
As you can see two view resolver classes are configured here.
ResourceBundleViewResolver resolves the views from the properties file. ResourceBundleViewResolver is the Implementation
of ViewResolver that uses bean definitions in a ResourceBundle, specified by the bundle base name, and for each view it
is supposed to resolve, it uses the value of the property [viewname].(class) as the view class and the value of the
property [viewname].url as the view url.
Here “basename” property has the value pdf-view which means properties file is named pdf-view.properties file.
pdf-view.properties
viewPDF.(class)=org.netjs.config.PDFView
Controller class handler method viewPDF returns logical view name as “viewPDF” which is used to resolve the view class using this properties file.
Another view resolver InternalResourceViewResolver is used to resolve the view name to JSPs.
Here note that both the Resolvers have a property order too which decides the priority. Lower order value means higher priority. Here ResourceBundleViewResolver has order set as 1 which means Spring framework will first try to resolve view using this class.
As a rule InternalResourceViewResolver should always have higher order value because it will always be resolved to view irrespective of value returned giving no chance to any other Resolver class.
Spring MVC PDF generation using OpenPDF
If you are using OpenPDF to generate PDF in your Spring MVC application then you need to make following changes.
Maven dependency
For OpenPDF you need to add following dependency in your pom.xml
<dependency> <groupId>com.github.librepdf</groupId> <artifactId>openpdf</artifactId> <version>1.2.3</version> </dependency>
PDF view class for OpenPDF
When using OpenPDF you can directly subclass AbstractPdfView as OpenPDF uses com.lowagie.* classes, no need to create your own Abstract class by extending AbstractView class.
import java.util.List; import java.util.Map; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import org.netjs.model.User; import org.springframework.web.servlet.view.document.AbstractPdfView; import com.lowagie.text.Document; import com.lowagie.text.Font; import com.lowagie.text.Phrase; import com.lowagie.text.pdf.PdfPCell; import com.lowagie.text.pdf.PdfPTable; import com.lowagie.text.pdf.PdfWriter; public class OpenPDFView extends AbstractPdfView{ @Override protected void buildPdfDocument(Map<String, Object> model, Document document, PdfWriter writer, HttpServletRequest request, HttpServletResponse response) throws Exception { System.out.println("buildPdfDocument in OpenPDFView"); // List of users that will be displayed in the PDF List<User> users = (List<User>)model.get("Users"); Font font = new Font(Font.HELVETICA, 18, Font.BOLDITALIC); PdfPTable table = new PdfPTable(3); table.setWidthPercentage(100.0f); table.setWidths(new float[] {4.0f, 4.0f, 4.0f}); PdfPCell cell = new PdfPCell(); cell.setPhrase(new Phrase("First Name", font)); table.addCell(cell); cell.setPhrase(new Phrase("Last Name", font)); table.addCell(cell); cell.setPhrase(new Phrase("Email", font)); table.addCell(cell); // adding rows for(User user : users) { table.addCell(user.getFirstName()); table.addCell(user.getLastName()); table.addCell(user.getEmail()); } // adding table to document document.add(table); } }
Properties class change for OpenPDF
In properties class referred by ResourceBundleViewResolver, now the view class should be this new PDF view class.
pdf-view.properties
viewPDF.(class)=org.netjs.config.OpenPDFView
Deploying and testing the application
Once the application is deployed to Tomcat server it can be accessed using the URL- http://localhost:8080/spring-mvc/getUsers
Generated PDF - Spring MVC PDF Generation Example
On clicking the View PDF button PDF is generated. Screen shot shows how it looks like in Chrome browser.
That's all for this topic Spring MVC PDF Generation Example. If you have any doubt or any suggestions to make please drop a comment. Thanks!
>>>Return to Spring Tutorial Page
Related Topics
You may also like-
I also expected that exist source code for download.very very thanks for this tutorial
ReplyDeleteThank you. This worked!
ReplyDelete