Skip to content

Software Development at Program Tom LTD

Place for coding, programming, development and software in general.

Menu
  • Blog
  • PDF Booklets
  • Dev Utils & Content
  • Java Spring Boot Or Web Apps
  • English
    • български
    • English
    • Español
    • Português
    • हिन्दी
    • Русский
    • Deutsch
    • Français
    • Italiano
    • العربية
  • About Us
  • Cart
Menu
Spring Boot CSV Export

Spring Boot CSV Export

Posted on June 22, 2025 by Toma Velev

Below is a Spring Boot example that demonstrates Export of data to CSV by processing records in two ways with and without library.

  • A count() query to get the total number of records.
  • Paging (PageRequest) to fetch records batch-by-batch.
  • Writing directly to the response’s OutputStream to avoid holding all records in memory.

✅ Assumptions

  • You have an entity called Person with id, name, and email.
  • You’re using Spring Data JPA and Spring Web.

📁 1. Entity Example

@Entity
public class Person {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    private String name;
    private String email;

    // Getters and setters
}

📁 2. Repository

public interface PersonRepository extends JpaRepository<Person, Long> {
}

📁 3. Service Layer for Batch Processing

@Service
public class CsvExportService {

    private final PersonRepository personRepository;

    public CsvExportService(PersonRepository personRepository) {
        this.personRepository = personRepository;
    }

    public void exportToCsv(OutputStream outputStream, int batchSize) throws IOException {
        long total = personRepository.count();
        long pages = (total + batchSize - 1) / batchSize;

        try (BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(outputStream));
             CSVPrinter csvPrinter = new CSVPrinter(writer, CSVFormat.DEFAULT
                     .withHeader("ID", "Name", "Email"))) {

            for (int page = 0; page < pages; page++) {
                Page<Person> persons = personRepository.findAll(PageRequest.of(page, batchSize));
                for (Person person : persons) {
                    csvPrinter.printRecord(person.getId(), person.getName(), person.getEmail());
                }
                csvPrinter.flush(); // Optional: write each batch to output
            }
        }
    }
}

🔸 Uses Apache Commons CSV (org.apache.commons.csv.CSVPrinter). Add it to your pom.xml:

<dependency>
    <groupId>org.apache.commons</groupId>
    <artifactId>commons-csv</artifactId>
    <version>1.10.0</version>
</dependency>

📁 4. Controller

@RestController
@RequestMapping("/api/export")
public class ExportController {

    private final CsvExportService csvExportService;

    public ExportController(CsvExportService csvExportService) {
        this.csvExportService = csvExportService;
    }

    @GetMapping("/persons")
    public void exportCsv(HttpServletResponse response,
                          @RequestParam(defaultValue = "100") int batchSize) throws IOException {
        response.setContentType("text/csv");
        response.setHeader("Content-Disposition", "attachment; filename=\"persons.csv\"");
        csvExportService.exportToCsv(response.getOutputStream(), batchSize);
    }
}

✅ Benefits

  • Memory-efficient: Only processes one batch at a time.
  • Scalable: Works well even for large datasets.
  • Streamed Output: Client starts receiving data immediately.

Would you like a version that exports to a file instead of an HTTP response?

Without external library

Here’s a complete solution without using any external libraries like Apache Commons CSV.

This should be used with caution. You need to be sure that your records are safe (no commas, quotes, or newlines inside fields). You can just write plain comma-separated lines manually.


✅ Key Points

  • Uses BufferedWriter to write directly to OutputStream.
  • No external dependencies.
  • Still paginates records to avoid memory issues.

📁 1. Service

@Service
public class CsvExportService {

    private final PersonRepository personRepository;

    public CsvExportService(PersonRepository personRepository) {
        this.personRepository = personRepository;
    }

    public void exportToCsv(OutputStream outputStream, int batchSize) throws IOException {
        long total = personRepository.count();
        long pages = (total + batchSize - 1) / batchSize;

        try (BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(outputStream))) {
            // Write CSV header
            writer.write("ID,Name,Email");
            writer.newLine();

            for (int page = 0; page < pages; page++) {
                Page<Person> persons = personRepository.findAll(PageRequest.of(page, batchSize));

                for (Person person : persons) {
                    writer.write(String.format("%d,%s,%s",
                        person.getId(),
                        person.getName(),
                        person.getEmail()));
                    writer.newLine();
                }

                writer.flush(); // Ensure each batch is flushed to client
            }
        }
    }
}

📁 2. Controller

@RestController
@RequestMapping("/api/export")
public class ExportController {

    private final CsvExportService csvExportService;

    public ExportController(CsvExportService csvExportService) {
        this.csvExportService = csvExportService;
    }

    @GetMapping("/persons")
    public void exportCsv(HttpServletResponse response,
                          @RequestParam(defaultValue = "100") int batchSize) throws IOException {
        response.setContentType("text/csv");
        response.setHeader("Content-Disposition", "attachment; filename=\"persons.csv\"");
        csvExportService.exportToCsv(response.getOutputStream(), batchSize);
    }
}

✅ Notes

  • You can adjust batchSize via query param: GET /api/export/persons?batchSize=500
  • Writing with BufferedWriter and flush() avoids memory pressure and keeps output stream moving.
  • No extra escaping is done — this assumes your fields do not contain commas or newlines.

Pagination Variation

Switching from PageRequest (which uses page number + size) to manual offset and limit (i.e., offset = batchSize * batchIndex) requires writing a custom query in your repository — because Spring Data JPA doesn’t support offset/limit out-of-the-box unless paging is used.


✅ Step-by-step: Export with OFFSET + LIMIT


📁 1. Modify Repository

Add a custom query with @Query and @Param annotations:

public interface PersonRepository extends JpaRepository<Person, Long> {

    @Query("SELECT p FROM Person p ORDER BY p.id ASC")
    List<Person> fetchBatch(@Param("offset") int offset, @Param("limit") int limit);

    @Query(value = "SELECT * FROM person ORDER BY id ASC LIMIT :limit OFFSET :offset", nativeQuery = true)
    List<Person> fetchBatchNative(@Param("offset") int offset, @Param("limit") int limit);
}

⚠️ Use the native query if you’re targeting a real SQL OFFSET + LIMIT pattern.
Otherwise, JPQL doesn’t support those directly, so the first version (fetchBatch) may not work depending on your JPA provider.


📁 2. Updated Service with Offset & Limit

@Service
public class CsvExportService {

    private final PersonRepository personRepository;

    public CsvExportService(PersonRepository personRepository) {
        this.personRepository = personRepository;
    }

    public void exportToCsv(OutputStream outputStream, int batchSize) throws IOException {
        long total = personRepository.count();

        try (BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(outputStream))) {
            // Header
            writer.write("ID,Name,Email");
            writer.newLine();

            for (int offset = 0; offset < total; offset += batchSize) {
                List<Person> batch = personRepository.fetchBatchNative(offset, batchSize);

                for (Person person : batch) {
                    writer.write(String.format("%d,%s,%s",
                        person.getId(),
                        person.getName(),
                        person.getEmail()));
                    writer.newLine();
                }

                writer.flush(); // Optional: push to client
            }
        }
    }
}

📁 3. Controller – no change

Same as before:

@GetMapping("/persons")
public void exportCsv(HttpServletResponse response,
                      @RequestParam(defaultValue = "100") int batchSize) throws IOException {
    response.setContentType("text/csv");
    response.setHeader("Content-Disposition", "attachment; filename=\"persons.csv\"");
    csvExportService.exportToCsv(response.getOutputStream(), batchSize);
}

✅ Summary

  • LIMIT/OFFSET logic avoids page calculation confusion.
  • More SQL-like and often easier to control in databases.
  • Native SQL is required for true LIMIT :limit OFFSET :offset support.
  • Prompt-to-Production: How AI is Forcing Us to Build Higher Quality Software
  • Debug Web View Flutter App
  • Skipping AI? You’re a Relic – Time to Evolve or Perish!
  • 2026 Flutter Launch Blueprint: Your 10-Step Checklist to App Store Domination
  • Product Requirements Document – for different software development levels

Categories

  • Apps (25)
  • ChatGPT (27)
  • Choosing a Framework (38)
  • Flutter (281)
  • Graphical User Interface (14)
  • Marketing (119)
  • Software Development (292)
  • Spring (45)
  • StartUp (22)
  • Uncategorized (14)
  • Uncategorized (4)
  • Vaadin (16)

Tags

Algorithms (9) crypto (29) flutterdev (39) General (86) Java (7) QR & Bar Codes (3) Software Dev Choices (33) Spring Boot (1) standards (1) Theme (3) User Authentication & Authorization (9) User Experience (10) Utilities (19) WordPress (11)

Product categories

  • All Technologies (87)
    • Flutter Apps (26)
    • GPT (4)
    • Java (39)
    • Native Android (3)
    • PHP (9)
    • Spring (Boot) / Quarkus (36)
    • Utils (15)
    • Vaadin 24+ (28)
    • Vaadin 8 (1)
  • Apps (18)
    • Employees DB (1)
    • Notes (6)
    • Personal Budget (1)
    • Recipes Book (1)
    • Stuff Organizer (1)
    • To-Do (2)
  • PDF Books (3)
  • Source Code Generators (8)

Recent Posts

  • Prompt-to-Production: How AI is Forcing Us to Build Higher Quality Software
  • Debug Web View Flutter App
  • Skipping AI? You’re a Relic – Time to Evolve or Perish!
  • 2026 Flutter Launch Blueprint: Your 10-Step Checklist to App Store Domination
  • Product Requirements Document – for different software development levels

Post Categories

  • Apps (25)
  • ChatGPT (27)
  • Choosing a Framework (38)
  • Flutter (281)
  • Graphical User Interface (14)
  • Marketing (119)
  • Software Development (292)
  • Spring (45)
  • StartUp (22)
  • Uncategorized (14)
  • Uncategorized (4)
  • Vaadin (16)