Close

Spring MVC - PDF View

[Last Updated: Oct 8, 2026]

This example demonstrates how to generate a PDF document and use it as the view technology in a Spring MVC application. We will use the iText library to create the document programmatically.

Example

Additional Maven Dependency

pom.xml

<dependency>
   <groupId>com.itextpdf</groupId>
   <artifactId>itext7-core</artifactId>
   <version>7.0.4</version>
</dependency>

Java Config class

In this example, we use BeanNameViewResolver to resolve the PDF view. This means we need to register an implementation of View as a bean and return that bean's name as the view name from the Spring controller. See this simple example of BeanNameViewResolver for more details.

package com.logicbig.example;

import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
import org.springframework.web.servlet.config.annotation.ViewResolverRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
import org.springframework.web.servlet.view.BeanNameViewResolver;
import org.springframework.web.servlet.view.InternalResourceViewResolver;

@EnableWebMvc
@Configuration
@ComponentScan
public class MyWebConfig implements WebMvcConfigurer {
    @Override
    public void configureViewResolvers(ViewResolverRegistry registry) {
        BeanNameViewResolver resolver = new BeanNameViewResolver();
        resolver.setOrder(0);
        registry.viewResolver(resolver);

        InternalResourceViewResolver viewResolver = new InternalResourceViewResolver();
        viewResolver.setPrefix("/WEB-INF/views/");
        viewResolver.setSuffix(".jsp");
        viewResolver.setOrder(1);
        registry.viewResolver(viewResolver);
    }
}

Note that InternalResourceViewResolver accepts any view name, so it must always be consulted last. BeanNameViewResolver returns null when no bean matches, which lets the next resolver try. The configuration above relies on registration order. In a real application, make the order explicit by calling setOrder(..) on both resolvers, giving BeanNameViewResolver the lower value.

Implementing the View

Spring provides the support class AbstractPdfView, but it is built on a legacy iText 2.x-style API (the com.lowagie package, as continued by OpenPDF). In addition, as of Spring Framework 7.0 the entire org.springframework.web.servlet.view.document package is deprecated (see the notes at the end of this tutorial). Instead, we extend AbstractView and use the iText API directly.

package com.logicbig.example;

import com.itextpdf.io.font.FontConstants;
import com.itextpdf.kernel.font.PdfFontFactory;
import com.itextpdf.kernel.pdf.PdfDocument;
import com.itextpdf.kernel.pdf.PdfWriter;
import com.itextpdf.layout.Document;
import com.itextpdf.layout.element.Paragraph;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.view.AbstractView;

import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import java.time.format.DateTimeFormatter;
import java.time.format.FormatStyle;
import java.util.Map;

@Component("reportView")
public class ReportPdfView extends AbstractView {

    @Override
    protected void renderMergedOutputModel(Map<String, Object> model,
                                           HttpServletRequest request,
                                           HttpServletResponse response) throws Exception {
        response.setContentType("application/pdf");
        response.setHeader("Content-Disposition", "attachment; filename=myReport.pdf");
        Report report = (Report) model.get("report");

        //IText API
        PdfWriter pdfWriter = new PdfWriter(response.getOutputStream());
        PdfDocument pdf = new PdfDocument(pdfWriter);
        Document pdfDocument = new Document(pdf);

        //title
        Paragraph title = new Paragraph(report.getName());
        title.setFont(PdfFontFactory.createFont(FontConstants.HELVETICA));
        title.setFontSize(18f);
        title.setItalic();
        pdfDocument.add(title);

        //date
        Paragraph date = new Paragraph(report.getDate().format(
                DateTimeFormatter.ofLocalizedDate(FormatStyle.FULL)));
        date.setFontSize(16f);
        pdfDocument.add(date);

        //content
        Paragraph content = new Paragraph(report.getContent());
        pdfDocument.add(content);

        pdfDocument.close();
    }
}
package com.logicbig.example;

import java.time.LocalDateTime;

public class Report {
    private String name;
    private String content;
    private LocalDateTime date;
    .............
}

Writing a Spring Controller

The controller returns the bean name reportView, which BeanNameViewResolver maps to our ReportPdfView bean. The model attribute report is read by the view when it renders the PDF.

package com.logicbig.example;

import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import java.time.LocalDateTime;

@Controller
@RequestMapping("/")
public class MyController {

    @GetMapping("/")
    public String mainView() {
        return "main";
    }

    @GetMapping("/report")
    public String handleForexRequest(Model model) {
        model.addAttribute("report", getReport());
        return "reportView";
    }

    public Report getReport() {
        //dummy report
        Report report = new Report();
        report.setName("My Report");
        report.setContent(
                "Lorem ipsum dolor sit amet, consectetuer adipiscing elit. "
                        + "Aenean commodo ligula eget dolor. Aenean massa.");
        report.setDate(LocalDateTime.now());
        return report;
    }
}

A page with a link to the report

src/main/webapp/WEB-INF/views/main.jsp

<html>
<body>
<h2>Spring Pdf View Example</h2>
<a href="/report">Get the Report</a>

</body>
</html>

Output

Clicking on the link downloads the generated PDF:

Tests

package com.logicbig.example;

import com.itextpdf.kernel.pdf.PdfDocument;
import com.itextpdf.kernel.pdf.PdfReader;
import com.itextpdf.kernel.pdf.canvas.parser.PdfTextExtractor;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit.jupiter.SpringExtension;
import org.springframework.test.context.web.WebAppConfiguration;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.setup.MockMvcBuilders;
import org.springframework.web.context.WebApplicationContext;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.time.LocalDate;
import java.time.format.DateTimeFormatter;
import java.time.format.FormatStyle;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.*;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*;

@ExtendWith(SpringExtension.class)
@WebAppConfiguration
@ContextConfiguration(classes = MyWebConfig.class)
public class MyControllerTest {

    @Autowired
    private WebApplicationContext webApplicationContext;

    private MockMvc mockMvc;

    @BeforeEach
    public void setUp() {
        mockMvc = MockMvcBuilders.webAppContextSetup(webApplicationContext)
                                 .build();
    }

    @Test
    public void reportIsDownloadedAsPdfAttachment() throws Exception {
        mockMvc.perform(get("/report"))
               .andExpect(status().isOk())
               .andExpect(header().string(
                       "Content-Disposition",
                       containsString("filename=myReport.pdf")));
    }

    @Test
    public void reportBodyIsAPdfFile() throws Exception {
        assertThat(new String(mockMvc.perform(get("/report"))
                                     .andExpect(status().isOk())
                                     .andReturn()
                                     .getResponse()
                                     .getContentAsByteArray(),
                              StandardCharsets.ISO_8859_1),
                   startsWith("%PDF-"));
    }

    @Test
    public void reportContainsTitleDateAndContent() throws Exception {
        assertThat(extractNormalizedText(
                           mockMvc.perform(get("/report"))
                                  .andExpect(status().isOk())
                                  .andReturn()
                                  .getResponse()
                                  .getContentAsByteArray()),
                   allOf(containsString("My Report"),
                         containsString(
                                 LocalDate.now()
                                          .format(DateTimeFormatter.ofLocalizedDate(
                                                  FormatStyle.FULL))),
                         containsString(
                                 "Lorem ipsum dolor sit amet, "
                                         + "consectetuer adipiscing elit."),
                         containsString(
                                 "Aenean commodo ligula eget dolor. "
                                         + "Aenean massa.")));
    }

    @Test
    public void reportContentTypeIsPdf() throws Exception {
        mockMvc.perform(get("/report"))
               .andExpect(status().isOk())
               .andExpect(content().contentType("application/pdf"));
    }

    private static String extractNormalizedText(byte[] pdfBytes)
            throws IOException {
        try (PdfDocument pdfDocument =
                     new PdfDocument(new PdfReader(new ByteArrayInputStream(
                             pdfBytes)))) {
            return PdfTextExtractor.getTextFromPage(
                                           pdfDocument.getPage(1))
                                   .replaceAll("\\s+", " ");
        }
    }
}
mvn clean test -Dtest="MyControllerTest"

Output (filtered)

$ mvn clean test -Dtest="MyControllerTest"
[INFO] -------------------------------------------------------
[INFO] T E S T S
[INFO] -------------------------------------------------------
[INFO] Running com.logicbig.example.MyControllerTest
[INFO] Tests run: 4, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 2.849 s -- in com.logicbig.example.MyControllerTest
[INFO]
[INFO] Results:
[INFO]
[INFO] Tests run: 4, Failures: 0, Errors: 0, Skipped: 0
[INFO]
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time: 14.852 s
[INFO] Finished at: 2026-10-06T05:09:11-05:00
[INFO] ------------------------------------------------------------------------

Versions Notes

  • Spring document views are deprecated. As of Spring Framework 7.0, all view classes in org.springframework.web.servlet.view.document (AbstractPdfView, AbstractXlsView, and related classes) are deprecated, and removal is planned for Spring Framework 7.1 or 7.2. Spring's recommendation is that libraries provide their own View implementations, or that applications render directly in web handlers. The approach in this tutorial (a custom AbstractView subclass) follows that recommendation and does not depend on the deprecated package.
  • iText artifact and API. The itext7-core artifact has been moved to com.itextpdf:itext-core (a POM-type dependency), and the current line is iText 9.x. In current iText versions, FontConstants is replaced by com.itextpdf.io.font.constants.StandardFonts. Check the current iText documentation for any other API differences.
  • iText licensing. iText is dual-licensed under AGPL and a commercial license. The AGPL is a copyleft license, so check that it is compatible with your project before using it in production. OpenPDF is a commonly used alternative with a more permissive license.
  • Content type. AbstractView defaults to an HTML content type. Call setContentType("application/pdf") on the view (for example, in its constructor) so the response is labeled correctly.
  • Resource handling. Create the iText PdfDocument and Document in a try-with-resources block so they are closed even if rendering fails.
  • JSP. The sample uses JSP only for the landing page. JSP still works with Spring MVC but is a legacy choice for new projects. Template engines such as Thymeleaf are generally preferred. A plain static HTML page would also work for this example.

Example Project

Dependencies and Technologies Used:

  • spring-webmvc 7.0.6 (Spring Web MVC)
     Version Compatibility: 3.2.9.RELEASE - 7.0.6Version List
    ×

    Version compatibilities of spring-webmvc with this example:

      javax.servlet-api:3.x
    • 3.2.9.RELEASE
    • 3.2.10.RELEASE
    • 3.2.11.RELEASE
    • 3.2.12.RELEASE
    • 3.2.13.RELEASE
    • 3.2.14.RELEASE
    • 3.2.15.RELEASE
    • 3.2.16.RELEASE
    • 3.2.17.RELEASE
    • 3.2.18.RELEASE
    • 4.0.0.RELEASE
    • 4.0.1.RELEASE
    • 4.0.2.RELEASE
    • 4.0.3.RELEASE
    • 4.0.4.RELEASE
    • 4.0.5.RELEASE
    • 4.0.6.RELEASE
    • 4.0.7.RELEASE
    • 4.0.8.RELEASE
    • 4.0.9.RELEASE
    • 4.1.0.RELEASE
    • 4.1.1.RELEASE
    • 4.1.2.RELEASE
    • 4.1.3.RELEASE
    • 4.1.4.RELEASE
    • 4.1.5.RELEASE
    • 4.1.6.RELEASE
    • 4.1.7.RELEASE
    • 4.1.8.RELEASE
    • 4.1.9.RELEASE
    • 4.2.0.RELEASE
    • 4.2.1.RELEASE
    • 4.2.2.RELEASE
    • 4.2.3.RELEASE
    • 4.2.4.RELEASE
    • 4.2.5.RELEASE
    • 4.2.6.RELEASE
    • 4.2.7.RELEASE
    • 4.2.8.RELEASE
    • 4.2.9.RELEASE
    • 4.3.0.RELEASE
    • 4.3.1.RELEASE
    • 4.3.2.RELEASE
    • 4.3.3.RELEASE
    • 4.3.4.RELEASE
    • 4.3.5.RELEASE
    • 4.3.6.RELEASE
    • 4.3.7.RELEASE
    • 4.3.8.RELEASE
    • 4.3.9.RELEASE
    • 4.3.10.RELEASE
    • 4.3.11.RELEASE
    • 4.3.12.RELEASE
    • 4.3.13.RELEASE
    • 4.3.14.RELEASE
    • 4.3.15.RELEASE
    • 4.3.16.RELEASE
    • 4.3.17.RELEASE
    • 4.3.18.RELEASE
    • 4.3.19.RELEASE
    • 4.3.20.RELEASE
    • 4.3.21.RELEASE
    • 4.3.22.RELEASE
    • 4.3.23.RELEASE
    • 4.3.24.RELEASE
    • 4.3.25.RELEASE
    • 4.3.26.RELEASE
    • 4.3.27.RELEASE
    • 4.3.28.RELEASE
    • 4.3.29.RELEASE
    • 4.3.30.RELEASE
    • 5.0.0.RELEASE
    • 5.0.1.RELEASE
    • 5.0.2.RELEASE
    • 5.0.3.RELEASE
    • 5.0.4.RELEASE
    • 5.0.5.RELEASE
    • 5.0.6.RELEASE
    • 5.0.7.RELEASE
    • 5.0.8.RELEASE
    • 5.0.9.RELEASE
    • 5.0.10.RELEASE
    • 5.0.11.RELEASE
    • 5.0.12.RELEASE
    • 5.0.13.RELEASE
    • 5.0.14.RELEASE
    • 5.0.15.RELEASE
    • 5.0.16.RELEASE
    • 5.0.17.RELEASE
    • 5.0.18.RELEASE
    • 5.0.19.RELEASE
    • 5.0.20.RELEASE
    • 5.1.0.RELEASE
    • 5.1.1.RELEASE
    • 5.1.2.RELEASE
    • 5.1.3.RELEASE
    • 5.1.4.RELEASE
    • 5.1.5.RELEASE
    • 5.1.6.RELEASE
    • 5.1.7.RELEASE
    • 5.1.8.RELEASE
    • 5.1.9.RELEASE
    • 5.1.10.RELEASE
    • 5.1.11.RELEASE
    • 5.1.12.RELEASE
    • 5.1.13.RELEASE
    • 5.1.14.RELEASE
    • 5.1.15.RELEASE
    • 5.1.16.RELEASE
    • 5.1.17.RELEASE
    • 5.1.18.RELEASE
    • 5.1.19.RELEASE
    • 5.1.20.RELEASE
    • 5.2.0.RELEASE
    • 5.2.1.RELEASE
    • 5.2.2.RELEASE
    • 5.2.3.RELEASE
    • 5.2.4.RELEASE
    • 5.2.5.RELEASE
    • 5.2.6.RELEASE
    • 5.2.7.RELEASE
    • 5.2.8.RELEASE
    • 5.2.9.RELEASE
    • 5.2.10.RELEASE
    • 5.2.11.RELEASE
    • 5.2.12.RELEASE
    • 5.2.13.RELEASE
    • 5.2.14.RELEASE
    • 5.2.15.RELEASE
    • 5.2.16.RELEASE
    • 5.2.17.RELEASE
    • 5.2.18.RELEASE
    • 5.2.19.RELEASE
    • 5.2.20.RELEASE
    • 5.2.21.RELEASE
    • 5.2.22.RELEASE
    • 5.2.23.RELEASE
    • 5.2.24.RELEASE
    • 5.2.25.RELEASE
    • 5.3.0
    • 5.3.1
    • 5.3.2
    • 5.3.3
    • 5.3.4
    • javax.servlet-api:4.x
    • 5.3.5
    • 5.3.6
    • 5.3.7
    • 5.3.8
    • 5.3.9
    • 5.3.10
    • 5.3.11
    • 5.3.12
    • 5.3.13
    • 5.3.14
    • 5.3.15
    • 5.3.16
    • 5.3.17
    • 5.3.18
    • 5.3.19
    • 5.3.20
    • 5.3.21
    • 5.3.22
    • 5.3.23
    • 5.3.24
    • 5.3.25
    • 5.3.26
    • 5.3.27
    • 5.3.28
    • 5.3.29
    • 5.3.30
    • 5.3.31
    • 5.3.32
    • 5.3.33
    • 5.3.34
    • 5.3.35
    • 5.3.36
    • 5.3.37
    • 5.3.38
    • 5.3.39
    • javax.* -> jakarta.*
      jakarta.servlet-api:6.x
      Java 17 min
    • 6.0.0
    • 6.0.1
    • 6.0.2
    • 6.0.3
    • 6.0.4
    • 6.0.5
    • 6.0.6
    • 6.0.7
    • 6.0.8
    • 6.0.9
    • 6.0.10
    • 6.0.11
    • 6.0.12
    • 6.0.13
    • 6.0.14
    • 6.0.15
    • 6.0.16
    • 6.0.17
    • 6.0.18
    • 6.0.19
    • 6.0.20
    • 6.0.21
    • 6.0.22
    • 6.0.23
    • 6.1.0
    • 6.1.1
    • 6.1.2
    • 6.1.3
    • 6.1.4
    • 6.1.5
    • 6.1.6
    • 6.1.7
    • 6.1.8
    • 6.1.9
    • 6.1.10
    • 6.1.11
    • 6.1.12
    • 6.1.13
    • 6.1.14
    • 6.1.15
    • 6.1.16
    • 6.1.17
    • 6.1.18
    • 6.1.19
    • 6.1.20
    • 6.1.21
    • 6.2.0
    • 6.2.1
    • 6.2.2
    • 6.2.3
    • 6.2.4
    • 6.2.5
    • 6.2.6
    • 6.2.7
    • 6.2.8
    • 6.2.9
    • 6.2.10
    • 6.2.11
    • 6.2.12
    • 6.2.13
    • 6.2.14
    • 6.2.15
    • 6.2.16
    • 6.2.17
    • 6.2.18
    • 6.2.19
    • 7.0.0
    • 7.0.1
    • 7.0.2
    • 7.0.3
    • 7.0.4
    • 7.0.5
    • 7.0.6

    Versions in green have been tested.

  • spring-test 7.0.6 (Spring TestContext Framework)
  • junit-jupiter-engine 6.0.3 (Module "junit-jupiter-engine" of JUnit)
  • hamcrest 3.0 (Core API and libraries of hamcrest matcher framework)
  • jakarta.servlet-api 6.1.0 (Jakarta Servlet API documentation)
  • itext7-core 7.0.4 (A Free Java-PDF library)
  • JDK 25
  • Maven 3.9.11

Spring PDF View Example Select All Download
  • spring-pdf-view-example
    • src
      • main
        • java
          • com
            • logicbig
              • example
                • ReportPdfView.java
          • webapp
            • WEB-INF
              • views
        • test
          • java
            • com
              • logicbig
                • example

    See Also

    Join