Spring MVC PDF View Example
This tutorial demonstrates how to create a PDF view using Spring MVC and itext. Out of the box spring only supports the older com.lowagie:itext
library. The newest com.itextpdf:itext
library is not supported by default. But there is a work around, which we’ll show you in the following example.
Maven Dependencies
We manage our application using Apache Maven. Add the following dependencies to your pom.xml
and maven will automatically manage and resolve the dependencies. Note: we added two dependencies for creating PDF documents. The first com.lowagie:itext
is the older open source library, which is by default supported by Spring. The second library com.itextpdf:itext
is not supported by default and you’ll need a licence to run this in production.
<?xml version="1.0" encoding="UTF-8"?>
<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>com.memorynotfound.spring.mvc</groupId>
<artifactId>pdf-content-negotiation</artifactId>
<version>1.0.0-SNAPSHOT</version>
<name>SPRING-MVC - ${project.artifactId}</name>
<url>https://memorynotfound.com</url>
<packaging>war</packaging>
<properties>
<encoding>UTF-8</encoding>
<spring.version>4.2.6.RELEASE</spring.version>
</properties>
<dependencies>
<!-- spring libraries -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>${spring.version}</version>
</dependency>
<!-- Needed for Lowagie PDF View -->
<dependency>
<groupId>com.lowagie</groupId>
<artifactId>itext</artifactId>
<version>2.1.7</version>
</dependency>
<!-- Needed for IText PDF View -->
<dependency>
<groupId>com.itextpdf</groupId>
<artifactId>itextpdf</artifactId>
<version>5.5.9</version>
</dependency>
<!-- servlet api -->
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version>3.1.0</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>jstl</artifactId>
<version>1.2</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.5.1</version>
<configuration>
<source>1.6</source>
<target>1.6</target>
</configuration>
</plugin>
<plugin>
<artifactId>maven-war-plugin</artifactId>
<version>2.6</version>
<configuration>
<failOnMissingWebXml>false</failOnMissingWebXml>
</configuration>
</plugin>
</plugins>
</build>
</project>
Spring MVC PDF View Configuration
The ServletInitializer
is a replacement for the web.xml
servlet descriptor. This class extends from the AbstractAnnotationConfigDispatcherServletInitializer
. By overriding the getServletConfigClasses()
we instruct where the spring configuration files are located.
package com.memorynotfound.config;
import org.springframework.web.servlet.support.AbstractAnnotationConfigDispatcherServletInitializer;
public class ServletInitializer extends AbstractAnnotationConfigDispatcherServletInitializer {
@Override
protected Class<?>[] getServletConfigClasses() {
return new Class[] { WebConfig.class };
}
@Override
protected String[] getServletMappings() {
return new String[] { "/" };
}
@Override
protected Class<?>[] getRootConfigClasses() {
return null;
}
}
Next, we show how to configure spring using Java Configuration. Annotate the class with @Configuration
to mark this class as a configuration file. We can override the configureContentNegotiation(..)
method and use the ContentNegotiationConfigurer
to configure the content negotiation preferences.
The configureViewResolvers(..)
method is where we register the views for our application. You can either enable the ItextPdfView()
or the LowagiePdfView
which we’ll create later on in this tutorial.
package com.memorynotfound.config;
import com.memorynotfound.view.ItextPdfView;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.MediaType;
import org.springframework.web.servlet.config.annotation.*;
@EnableWebMvc
@Configuration
@ComponentScan("com.memorynotfound")
public class WebConfig extends WebMvcConfigurerAdapter{
@Override
public void configureContentNegotiation(ContentNegotiationConfigurer configurer) {
configurer
.defaultContentType(MediaType.TEXT_HTML)
.parameterName("type")
.favorParameter(true)
.ignoreUnknownPathExtensions(false)
.ignoreAcceptHeader(false)
.useJaf(true);
}
@Override
public void configureViewResolvers(ViewResolverRegistry registry) {
registry.jsp("/WEB-INF/views/", ".jsp");
registry.enableContentNegotiation(
new ItextPdfView()
// Use either ItextPdfView or LowagiePdfView
// new LowagiePdfView()
);
}
}
The equivalent Spring MVC XML Configuration is located below:
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:mvc="http://www.springframework.org/schema/mvc"
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="com.memorynotfound" />
<bean id="contentNegotiationManager" class="org.springframework.web.accept.ContentNegotiationManagerFactoryBean">
<property name="defaultContentType" value="TEXT_HTML"/>
<property name="parameterName" value="type"/>
<property name="favorParameter" value="true"/>
<property name="ignoreUnknownPathExtensions" value="false"/>
<property name="ignoreAcceptHeader" value="false"/>
<property name="useJaf" value="true"/>
</bean>
<mvc:view-resolvers>
<mvc:content-negotiation>
<mvc:default-views>
<bean class="com.memorynotfound.view.ItextPdfView"/>
</mvc:default-views>
</mvc:content-negotiation>
<mvc:jsp prefix="/WEB-INF/views/" suffix=".jsp"/>
</mvc:view-resolvers>
</beans>
Creating the controller
The CourseController
adds some data to the Model
which we’ll display on the views.
package com.memorynotfound.controller;
import com.memorynotfound.model.Course;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import java.util.Arrays;
import java.util.Date;
import java.util.List;
@Controller
public class CourseController {
List<Course> documents = Arrays.asList(
new Course(1, "Spring MVC PDF IText Example", new Date()),
new Course(3, "Spring MVC PDF Lowagie Example", new Date())
);
@RequestMapping(method = RequestMethod.GET)
public String getDocuments(Model model) {
model.addAttribute("courses", documents);
return "index";
}
}
package com.memorynotfound.model;
import java.util.Date;
public class Course {
private Integer id;
private String name;
private Date date;
public Course() {
}
public Course(Integer id, String name, Date date) {
this.id = id;
this.name = name;
this.date = date;
}
public Integer getId() {
return id;
}
public String getName() {
return name;
}
public Date getDate() {
return date;
}
}
Spring MVC PDF Lowagie View
The older com.lowagie:itext
library is supported by default. Create a new class and extend from the AbstractPdfView
. We can create the PDF Document using the buildPdfDocument(..)
method.
package com.memorynotfound.view;
import com.lowagie.text.Document;
import com.lowagie.text.pdf.PdfPTable;
import com.lowagie.text.pdf.PdfWriter;
import com.memorynotfound.model.Course;
import org.springframework.web.servlet.view.document.AbstractPdfView;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.text.DateFormat;
import java.util.List;
import java.util.Map;
public class LowagiePdfView extends AbstractPdfView {
private static final DateFormat DATE_FORMAT = DateFormat.getDateInstance(DateFormat.SHORT);
@Override
protected void buildPdfDocument(Map<String, Object> model,
Document document, PdfWriter writer,
HttpServletRequest request,
HttpServletResponse response) throws Exception {
@SuppressWarnings("unchecked")
List<Course> courses = (List<Course>) model.get("courses");
PdfPTable table = new PdfPTable(3);
table.addCell("ID");
table.addCell("Name");
table.addCell("Date");
for (Course course : courses){
table.addCell(String.valueOf(course.getId()));
table.addCell(course.getName());
table.addCell(DATE_FORMAT.format(course.getDate()));
}
document.add(table);
}
}
Spring MVC PDF IText Example
The new com.itextpdf:itextpdf
library is not supported by default. We can resolve this by first creating an abstract class and extending from the AbstractView
. In this class we set the correct content type for our document. Next, we create an abstract method that’ll be used for creating the PDF document. Finally, we override and implement the renderMergedOutputModel(..)
method that’ll write the pdf document to the response.
package com.memorynotfound.view;
import com.itextpdf.text.Document;
import com.itextpdf.text.PageSize;
import com.itextpdf.text.pdf.PdfWriter;
import org.springframework.web.servlet.view.AbstractView;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.ByteArrayOutputStream;
import java.util.Map;
public abstract class AbstractITextPdfView extends AbstractView {
public AbstractITextPdfView() {
setContentType("application/pdf");
}
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;
@Override
protected void renderMergedOutputModel(Map<String, Object> model,
HttpServletRequest request,
HttpServletResponse response) throws Exception {
ByteArrayOutputStream out = createTemporaryOutputStream();
Document document = new Document(PageSize.A4);
PdfWriter writer = PdfWriter.getInstance(document, out);
writer.setViewerPreferences(PdfWriter.ALLOW_PRINTING | PdfWriter.PageLayoutSinglePage);
buildPdfMetadata(model, document, request);
document.open();
buildPdfDocument(model, document, writer, request, response);
document.close();
writeToResponse(response, out);
}
@Override
protected boolean generatesDownloadContent() {
return true;
}
}
Create a new class and extend from the previously created AbstractItextPdfView
and override the buildPdfDocument(..)
method to create your PDF document.
package com.memorynotfound.view;
import java.text.DateFormat;
import java.util.List;
import java.util.Map;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import com.itextpdf.text.Document;
import com.itextpdf.text.pdf.PdfPTable;
import com.itextpdf.text.pdf.PdfWriter;
import com.memorynotfound.model.Course;
public class ItextPdfView extends AbstractITextPdfView {
private static final DateFormat DATE_FORMAT = DateFormat.getDateInstance(DateFormat.SHORT);
@Override
protected void buildPdfDocument(Map<String, Object> model,
Document document, PdfWriter writer, HttpServletRequest request,
HttpServletResponse response) throws Exception {
@SuppressWarnings("unchecked")
List<Course> courses = (List<Course>) model.get("courses");
PdfPTable table = new PdfPTable(3);
table.setWidths(new int[]{10, 60, 30});
table.addCell("ID");
table.addCell("Name");
table.addCell("Date");
for (Course course : courses){
table.addCell(String.valueOf(course.getId()));
table.addCell(course.getName());
table.addCell(DATE_FORMAT.format(course.getDate()));
}
document.add(table);
}
}
HTML View
To demonstrate the content negotiation we also created a simple HTML view to display the courses
attributes which we added to the Model
.
<%@ page language="java" contentType="text/html; charset=ISO-8859-1" pageEncoding="ISO-8859-1"%>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<%@ taglib prefix="fmt" uri="http://java.sun.com/jsp/jstl/fmt" %>
<html>
<head>
<title>Spring MVC PDF View Example</title>
</head>
<body>
<table>
<tr>
<th>ID</th>
<th>Name</th>
<th>Date</th>
</tr>
<c:forEach var="c" items="${courses}">
<tr>
<td>${c.id}</td>
<td>${c.name}</td>
<td><fmt:formatDate value="${c.date}" pattern="MM/dd/yy"/></td>
</tr>
</c:forEach>
</table>
</body>
</html>
Demo
URL: http://localhost:8081/spring-mvc-pdf/
URL: http://localhost:8081/spring-mvc-pdf/index.pdf
How can I change pdf path?
Thanks for example app!
XML config for view mapping provided in example doesn’t work.
Only java config WebConfig.java works.
Could you verify and fix that, please?
Hello,
I Just tested the Spring XML Configuration and it should work.
How are you importing the xml configuration?
You can add the configuration using following java configuration:
@Configuration
@ImportResource("classpath:app-config.xml")
public class WebConfig extends WebMvcConfigurerAdapter{
}
Make sure you don’t mix
@EnableMvc
andmvc:annotation-driven
Kr,
Memorynotfound