Saturday, July 16, 2022

SpringBoot API to return compressed CSV file

Here's an example of a Spring Boot Controller that accepts a request payload of type EmployeeRequest, generates a CSV file based on the input data, compresses the file into a ZIP, and returns the zipped file as a response:

 



Spring Boot Controller

java

import org.apache.commons.csv.CSVFormat; import org.apache.commons.csv.CSVPrinter; import org.springframework.http.HttpHeaders; import org.springframework.http.MediaType; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.*; import org.springframework.web.servlet.mvc.method.annotation.StreamingResponseBody; import java.io.*; import java.util.zip.ZipEntry; import java.util.zip.ZipOutputStream; @RestController @RequestMapping("/api/employee") public class EmployeeController { @PostMapping("/generate") public ResponseEntity<StreamingResponseBody> generateZippedCsv(@RequestBody EmployeeRequest request) { StreamingResponseBody responseBody = outputStream -> { // Create CSV in memory ByteArrayOutputStream csvOutputStream = new ByteArrayOutputStream(); try (CSVPrinter csvPrinter = new CSVPrinter(new PrintWriter(csvOutputStream), CSVFormat.DEFAULT)) { // Add CSV Headers csvPrinter.printRecord("First Name", "Last Name", "Middle Name", "Date of Birth"); // Populate CSV rows with request data for (Employee employee : request.getEmployees()) { csvPrinter.printRecord( employee.getFirstName(), employee.getLastName(), employee.getMiddleName(), employee.getDateOfBirth() ); } } // Create ZIP containing the CSV try (ZipOutputStream zipOutputStream = new ZipOutputStream(outputStream)) { zipOutputStream.putNextEntry(new ZipEntry("employees.csv")); zipOutputStream.write(csvOutputStream.toByteArray()); zipOutputStream.closeEntry(); } }; // Set headers for ZIP file download HttpHeaders headers = new HttpHeaders(); headers.setContentType(MediaType.APPLICATION_OCTET_STREAM); headers.set(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=\"employees.zip\""); return ResponseEntity.ok() .headers(headers) .body(responseBody); } }

Supporting Classes

EmployeeRequest

This class represents the input request payload that contains a list of employees.

java

import java.util.List; public class EmployeeRequest { private List<Employee> employees; // Getters and Setters public List<Employee> getEmployees() { return employees; } public void setEmployees(List<Employee> employees) { this.employees = employees; } }

Employee

This class represents the individual employee data.

java

public class Employee { private String firstName; private String lastName; private String middleName; private String dateOfBirth; // Use String for simplicity; can use LocalDate or Date instead // Getters and Setters 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 getMiddleName() { return middleName; } public void setMiddleName(String middleName) { this.middleName = middleName; } public String getDateOfBirth() { return dateOfBirth; } public void setDateOfBirth(String dateOfBirth) { this.dateOfBirth = dateOfBirth; } } 
 

How It Works

  1. Input: The API receives a JSON payload containing a list of employees with their details (firstNamelastNamemiddleName, and dateOfBirth).
  2. CSV Generation: It generates a CSV file with these details in memory.
  3. ZIP Compression: The CSV file is compressed into a ZIP archive.
  4. Response: The ZIP file is returned to the client with appropriate headers for file download.

  



Example Request

POST Request

http

POST /api/employee/generate Content-Type: application/json

Request Body

json

{ "employees": [ { "firstName": "John", "lastName": "Doe", "middleName": "A.", "dateOfBirth": "1985-03-15" }, { "firstName": "Jane", "lastName": "Smith", "middleName": "B.", "dateOfBirth": "1990-07-21" } ] }

Response

The API returns a employees.zip file containing the CSV file. The CSV will look like this:

csv

First Name,Last Name,Middle Name,Date of Birth John,Doe,A.,1985-03-15 Jane,Smith,B.,1990-07-21

 

1. Spring Boot Configuration Limits

Spring Boot uses Servlet multipart handling for file uploads and responses. The maximum file size is governed by the spring.servlet.multipart properties:

  • spring.servlet.multipart.max-file-size:

    • Limits the maximum size of a single uploaded file or generated response.
    • Default: 1MB.
  • spring.servlet.multipart.max-request-size:

    • Limits the total size of all files in a multipart request or response.
    • Default: 10MB.

Example Configuration in application.properties:

properties

spring.servlet.multipart.max-file-size=100MB spring.servlet.multipart.max-request-size=100MB

2. Server-Specific Limits

Your server configuration (e.g., Tomcat, Jetty) also enforces limits:

Tomcat:

  • server.tomcat.max-swallow-size: Specifies the maximum size of a request body or response.
    • Default: 2MB.
    • Set -1 for no limit.

Example:

properties

server.tomcat.max-swallow-size=-1

Other Servers:

  • Jetty, Undertow, or other embedded servers might have similar settings for request/response size limits. Check their documentation for details.

3. JVM Memory Limits

Your application must have enough memory to handle large files, especially for:

  • In-Memory Operations: When files are stored or manipulated in memory (e.g., CSV generation or zipping).
  • Heap Space: Ensure your JVM has adequate heap memory to avoid OutOfMemoryError.

JVM Options:

bash

-Xms512M -Xmx2G

This example sets the JVM to use 512MB initial memory and 2GB maximum memory.


4. Operating System Limits

The underlying operating system may impose limits on:

  • Temporary File Storage: For file uploads or in-memory buffers, check /tmp (or equivalent) storage space.
  • Maximum Open File Handles: Ensure you are not exceeding the file descriptor limits, particularly in high-concurrency scenarios.

5. Practical Limits for Download Responses

For downloading files (like a CSV zipped file):

  • StreamingResponseBody: Use it to stream large files directly to the response output stream, avoiding in-memory buffering.
  • This approach minimizes memory usage, allowing responses to exceed typical memory constraints.

6. Network Bandwidth

Large file downloads are also affected by:

  • Network bandwidth between the server and the client.
  • Timeouts due to prolonged transfers.

Ensure:

  • Timeouts are configured appropriately (server.connection-timeout in Spring Boot).
  • Clients can handle large file downloads.

Summary

  • Default Spring Boot limits are 1MB per file and 10MB per request.
  • You can increase these limits using properties like spring.servlet.multipart.max-file-size.
  • Use StreamingResponseBody for large file downloads to avoid memory bottlenecks.
  • Monitor JVM memory, server configurations, and OS limits to ensure performance and reliability.

If you are working with extremely large files (e.g., several GB), consider a more robust solution like:

  • Direct S3 File Downloads: Upload the file to AWS S3 and share a pre-signed URL.
  • Chunked File Downloads: Break the file into smaller chunks for transmission.

Amazon Bedrock and AWS Rekognition comparison for Image Recognition

 Both Amazon Bedrock and AWS Rekognition are services provided by AWS, but they cater to different use cases, especially when it comes to ...