java 103 lines · 3 tabs

Stream a Large CSV Export to the HTTP Response with StreamingResponseBody in Spring Boot

Shared by codesnips Aug 2026
3 tabs
@RestController
@RequestMapping("/api/transactions")
public class TransactionExportController {

    private final CsvExportService exportService;

    public TransactionExportController(CsvExportService exportService) {
        this.exportService = exportService;
    }

    @GetMapping(value = "/export", produces = "text/csv")
    public ResponseEntity<StreamingResponseBody> export(
            @RequestParam("accountId") long accountId) {

        String filename = "transactions-" + accountId + ".csv";

        StreamingResponseBody body = outputStream ->
                exportService.writeTo(accountId, outputStream);

        return ResponseEntity.ok()
                .header(HttpHeaders.CONTENT_DISPOSITION,
                        "attachment; filename=\"" + filename + "\"")
                .contentType(MediaType.parseMediaType("text/csv; charset=UTF-8"))
                .body(body);
    }
}
3 files · java Explain with highlit

This snippet shows how a Spring Boot application exports a potentially huge dataset as CSV without ever holding the whole result set in memory. The core idea is to keep the export lazy end-to-end: rows are pulled from the database with a streaming JDBC cursor, formatted incrementally, and written straight to the servlet output stream as they arrive. Because Spring MVC releases the request-handling thread while the body is produced, the container can serve other requests instead of blocking, and the response is flushed to the client in chunks rather than buffered.

In TransactionExportController, the endpoint returns a StreamingResponseBody rather than a List or a byte array. The Content-Disposition and Content-Type headers are set so the browser treats the payload as a downloadable CSV file. Returning ResponseEntity<StreamingResponseBody> lets the controller set status and headers up front while deferring body production to the callback, which Spring invokes on a separate thread once the response has been committed.

The actual writing lives in CsvExportService. The writeTo method wraps the OutputStream in a BufferedWriter and delegates row streaming to the repository. Each TransactionRow is escaped and written, and the writer is flushed periodically so bytes reach the socket without accumulating. Escaping in escape handles the classic CSV pitfalls — embedded commas, quotes, and newlines — by quoting fields and doubling internal quotes.

In TransactionRepository, streamAll uses Spring's JdbcTemplate with a forward-only, read-only statement configured with a JDBC fetchSize so the driver keeps a server-side cursor open instead of materializing every row. A RowCallbackHandler pushes each row through a Consumer, meaning no intermediate collection is built.

The main trade-off is that the response is committed early, so errors mid-stream cannot change the HTTP status — the download simply truncates. The database connection and cursor stay open for the whole download, so slow clients tie up a connection; a bounded pool and sensible timeouts matter. This pattern is the right tool when result sets are large or unbounded and latency-to-first-byte and steady memory use matter more than transactional neatness.


Related snips

typescript
export interface RetryOptions {
  retries: number;
  baseMs: number;
  maxMs: number;
  signal?: AbortSignal;
  onRetry?: (attempt: number, delay: number, err: unknown) => void;

Exponential backoff with jitter for retries

typescript reliability retry
by codesnips 2 tabs
ruby
require "csv"

class PeopleCsvStream
  include Enumerable

  HEADERS = %w[id full_name email signed_up_at plan].freeze

Resilient CSV Export as a Streamed Response

rails performance streaming
by codesnips 3 tabs
graphql
type User {
    id: ID!
    name: String!
    email: String!
    posts: [Post!]!
    createdAt: String!

GraphQL API with Spring Boot

java graphql spring-boot
by David Kumar 3 tabs
javascript
// Creating a Promise
const myPromise = new Promise((resolve, reject) => {
  const success = true;

  setTimeout(() => {
    if (success) {

Promises and async/await patterns for asynchronous JavaScript

javascript promises async-await
by Alex Chang 1 tab
typescript
export type Settled<R> =
  | { status: 'fulfilled'; value: R }
  | { status: 'rejected'; reason: unknown };

export interface ConcurrencyOptions {
  limit: number;

Simple concurrency limiter for batch operations

node concurrency async
by codesnips 2 tabs
java
package com.example.starter.config;

import com.example.starter.properties.CustomProperties;
import com.example.starter.service.CustomService;
import org.springframework.boot.autoconfigure.AutoConfiguration;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;

Custom Spring Boot starters

java spring-boot starter
by David Kumar 4 tabs

Share this code

Here's the card — post it anywhere.

Stream a Large CSV Export to the HTTP Response with StreamingResponseBody in Spring Boot — share card
Link copied