Wednesday, January 27, 2021

Spring MVC PDF Generation Example

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

  1. 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.
  2. 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 dependencies

Maven is used for managing dependencies in this Spring MVC PDF generation example.

<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

Spring MVC PDF generation

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.

Spring MVC pdf generation

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

  1. Spring MVC File Download Example
  2. Spring MVC Redirect Example
  3. Spring MVC Exception Handling - @ExceptionHandler And @ControllerAdvice Example
  4. Spring MVC @PathVariable Example - Creating Dynamic URL
  5. Spring Transaction Management Example - @Transactional Annotation and JDBC

You may also like-

  1. Spring NamedParameterJdbcTemplate Select Query Example
  2. Spring Batch Processing Using JDBCTemplate batchUpdate() Method
  3. ServiceLocatorFactoryBean in Spring
  4. Benefits, Disadvantages And Limitations of Autowiring in Spring
  5. Java CyclicBarrier With Examples
  6. How to Sort ArrayList in Java
  7. How to Create Immutable Class in Java
  8. YARN in Hadoop

2 comments:

  1. I also expected that exist source code for download.very very thanks for this tutorial

    ReplyDelete