A Comprehensive Guide to Generating and Returning PDF Files Using Spring MVC and iTextPDF

Dec 03, 2025 · Programming · 10 views · 7.8

Keywords: Spring MVC | PDF generation | iTextPDF | HTTP response | byte stream handling

Abstract: This article provides an in-depth exploration of dynamically generating and returning PDF files within the Spring MVC framework. By analyzing common error patterns, it explains how to properly configure HTTP response headers, handle byte stream transmission, and optimize file generation logic to avoid concurrency issues. Based on the iTextPDF library, it offers complete code examples from JSON data parsing to PDF generation, emphasizing best practices in reactive programming and resource management.

Introduction

Dynamic PDF generation is a common requirement in modern web applications, such as for report creation, data export, or document printing. Spring MVC, as a robust Java web framework, offers flexible mechanisms to handle such tasks. However, many developers encounter issues with improper response stream handling or flawed file generation logic when implementing PDF return functionality. This article will dissect a typical scenario to detail how to correctly use Spring MVC and the iTextPDF library to generate and return PDF files.

Analysis of Common Error Patterns

A key mistake in initial implementations is the failure to correctly write generated PDF content into the HTTP response stream. For example, the following code snippet sets response headers but does not actually output PDF data:

@RequestMapping(value="/getpdf", method=RequestMethod.POST)
public Document getPDF(HttpServletRequest request, HttpServletResponse response, @RequestBody String json) throws Exception {
    response.setContentType("application/pdf");
    response.setHeader("Content-Disposition", "attachment:filename=report.pdf");
    OutputStream out = response.getOutputStream();
    Document doc = PdfUtil.showHelp(emp);
    return doc;
}

The main issue here is that the showHelp method writes the PDF to the local file system (e.g., C:/tmp/report.pdf) but does not transmit this file content to response.getOutputStream(). Consequently, the client receives an empty response body, causing download failure. Additionally, directly returning a Document object instead of a byte stream does not conform to standard HTTP response formats.

Correct Implementation Approach

To address these problems, we need to write PDF content directly into the response stream as a byte array. Spring MVC's ResponseEntity class provides an elegant solution for this. Here is the improved core code:

@RequestMapping(value="/getpdf", method=RequestMethod.POST)
public ResponseEntity<byte[]> getPDF(@RequestBody String json) {
    // Convert JSON data to a business object
    Employee emp = convertJsonToEmployee(json);
    
    // Generate the PDF file (note: optimize to avoid file system dependency)
    String filePath = PdfUtil.generatePDF(emp);
    
    // Read PDF file content into a byte array
    byte[] contents = readFileToByteArray(filePath);
    
    // Set HTTP response headers
    HttpHeaders headers = new HttpHeaders();
    headers.setContentType(MediaType.APPLICATION_PDF);
    String filename = "report_" + System.currentTimeMillis() + ".pdf";
    headers.setContentDispositionFormData(filename, filename);
    headers.setCacheControl("must-revalidate, post-check=0, pre-check=0");
    
    // Return response with PDF data
    return new ResponseEntity<>(contents, headers, HttpStatus.OK);
}

In this implementation, we first parse the incoming JSON string into an Employee object (assuming convertJsonToEmployee uses a library like Jackson for deserialization). Then, we call the PdfUtil.generatePDF method to generate the PDF file and return its file path. Note that a timestamp is added to the filename to prevent overwriting in concurrent requests. Next, the readFileToByteArray method (implementable via Files.readAllBytes) reads the PDF content into a byte array. Finally, ResponseEntity encapsulates the data, response headers, and status code, ensuring the client can correctly receive and download the PDF file.

Optimization of PDF Generation Logic

The original showHelp method has two main issues: it hardcodes file writing to a specific path (e.g., C:/tmp/report.pdf), which may cause permission errors or missing path issues; and the method name does not clearly reflect its functionality. Here is an optimized version of the generatePDF method:

public static String generatePDF(Employee emp) throws Exception {
    // Use temporary files to avoid concurrency conflicts
    String filename = "report_" + UUID.randomUUID().toString() + ".pdf";
    String filePath = System.getProperty("java.io.tmpdir") + File.separator + filename;
    
    Document document = new Document();
    PdfWriter.getInstance(document, new FileOutputStream(filePath));
    document.open();
    
    // Add document content
    document.add(new Paragraph("Employee Report"));
    document.add(new Paragraph("Generated on: " + new Date().toString()));
    
    // Create table and populate data
    PdfPTable table = new PdfPTable(2);
    PdfPCell headerCell = new PdfPCell(new Paragraph("Employee Data"));
    headerCell.setColspan(2);
    headerCell.setHorizontalAlignment(Element.ALIGN_CENTER);
    headerCell.setBackgroundColor(new BaseColor(140, 221, 8));
    table.addCell(headerCell);
    
    // Assume Employee object has getId() and getName() methods
    table.addCell("ID");
    table.addCell(emp.getId());
    table.addCell("Name");
    table.addCell(emp.getName());
    
    document.add(table);
    document.close();
    
    return filePath;
}

This optimized version uses UUID to generate unique filenames and stores files in the system temporary directory, improving concurrency safety and portability. The method name is changed to generatePDF to better describe its functionality. Document content is dynamically generated based on the Employee object, enhancing practicality.

Advanced Considerations and Best Practices

For high-performance applications, direct file system operations can become a bottleneck. We can further optimize by writing PDF content directly to an in-memory byte stream, avoiding disk I/O. Here is an example using ByteArrayOutputStream:

public static byte[] generatePDFInMemory(Employee emp) throws Exception {
    ByteArrayOutputStream baos = new ByteArrayOutputStream();
    Document document = new Document();
    PdfWriter.getInstance(document, baos);
    document.open();
    
    // Add document content (same as above)
    document.add(new Paragraph("In-Memory PDF Generation"));
    // ... other content addition logic
    
    document.close();
    return baos.toByteArray();
}

In the controller, this method can be used directly to return a byte array without temporary files:

byte[] contents = PdfUtil.generatePDFInMemory(emp);

Additionally, error handling should be considered: for instance, returning appropriate HTTP error statuses (e.g., 400 Bad Request or 500 Internal Server Error) when JSON parsing fails or PDF generation throws exceptions. Spring's @ExceptionHandler can uniformly manage such exceptions.

Conclusion

Through this discussion, we have clarified the key steps to return generated PDFs in Spring MVC: correctly setting response headers, transmitting PDF content as a byte stream, and optimizing file generation logic to avoid concurrency issues. Using ResponseEntity<byte[]> with the iTextPDF library provides an efficient and maintainable solution. Developers should choose between file system or in-memory generation based on specific needs and always prioritize code clarity and error handling. These practices are not only applicable to PDF generation but can also extend to other binary file return scenarios, such as images or Excel documents.

Copyright Notice: All rights in this article are reserved by the operators of DevGex. Reasonable sharing and citation are welcome; any reproduction, excerpting, or re-publication without prior permission is prohibited.