java
53 lines · 9 steps
Streaming a CSV export in Spring
A Spring endpoint streams orders straight to the HTTP response as CSV without buffering them all in memory.
Explained by
highlit
1@RestController
2@RequestMapping("/api/reports")
3public class OrderReportController {
4
5 private final OrderRepository orderRepository;
6
7 public OrderReportController(OrderRepository orderRepository) {
8 this.orderRepository = orderRepository;
9 }
10
11 @GetMapping("/orders")
12 public ResponseEntity<StreamingResponseBody> streamOrders(
13 @RequestParam(defaultValue = "#{T(java.time.LocalDate).now().minusMonths(1)}") LocalDate from,
14 @RequestParam(defaultValue = "#{T(java.time.LocalDate).now()}") LocalDate to) {
15
16 StreamingResponseBody body = out -> {
17 var writer = new BufferedWriter(new OutputStreamWriter(out, StandardCharsets.UTF_8));
18 writer.write("id,customer,placed_at,status,total\n");
19
20 try (Stream<Order> orders = orderRepository.streamByPlacedAtBetween(from.atStartOfDay(), to.atTime(LocalTime.MAX))) {
21 orders.forEach(order -> {
22 try {
23 writer.write(String.join(",",
24 Long.toString(order.getId()),
25 escape(order.getCustomerName()),
26 order.getPlacedAt().format(DateTimeFormatter.ISO_LOCAL_DATE_TIME),
27 order.getStatus().name(),
28 order.getTotal().toPlainString()));
29 writer.write('\n');
30 } catch (IOException e) {
31 throw new UncheckedIOException(e);
32 }
33 });
34 }
35 writer.flush();
36 };
37
38 String filename = "orders-%s-to-%s.csv".formatted(from, to);
39 return ResponseEntity.ok()
40 .contentType(new MediaType("text", "csv", StandardCharsets.UTF_8))
41 .header(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=\"" + filename + "\"")
42 .cacheControl(CacheControl.noCache())
43 .body(body);
44 }
45
46 private static String escape(String value) {
47 if (value == null) return "";
48 if (value.contains(",") || value.contains("\"") || value.contains("\n")) {
49 return '"' + value.replace("\"", "\"\"") + '"';
50 }
51 return value;
52 }
53}
01 / 01
STEP 01
‹ swipe to step through ›
Walkthrough
Space play
←→ step
click any line
Three takeaways
- 1Returning a StreamingResponseBody lets you write to the response as data arrives instead of materializing a whole payload.
- 2Pairing a JPA Stream inside a try-with-resources keeps the result set open only for the duration of the write.
- 3Setting Content-Disposition and a specific media type turns a plain endpoint into a browser-triggered file download.
Related explainers
java
public final class IbanValidator { private static final Map<String, Integer> COUNTRY_LENGTHS = Map.ofEntries( Map.entry("DE", 22), Map.entry("FR", 27), Map.entry("GB", 22),
Validating IBANs with the mod-97 checksum
validation
checksum
modular-arithmetic
Intermediate
8 steps
java
@Configuration @EnableWebSecurity public class OAuth2SecurityConfig {
How OIDC login works in Spring Security
oauth2
openid-connect
authorization
Intermediate
8 steps
java
public class SlidingLogRateLimiter { private final int maxRequests; private final long windowMillis;
How a sliding-log rate limiter works
rate-limiting
concurrency
sliding-window
Advanced
8 steps
typescript
import { Injectable, UnauthorizedException } from '@nestjs/common'; import { PassportStrategy } from '@nestjs/passport'; import { ExtractJwt, Strategy } from 'passport-jwt'; import { ConfigService } from '@nestjs/config';
How a JWT strategy authenticates in NestJS
authentication
jwt
passport
Intermediate
7 steps
php
<?php namespace App\Http\Controllers;
Streaming a filtered CSV export in Laravel
streaming
csv-export
query-builder
Intermediate
9 steps
java
public final class Slugifier { private static final Pattern NON_LATIN = Pattern.compile("[^\\w-]"); private static final Pattern WHITESPACE = Pattern.compile("[\\s]+");
Building a URL slugifier in Java
regex
unicode-normalization
string-processing
Intermediate
8 steps
Share this explainer
Here's the card — post it anywhere.
Made with highlit — turn any snippet into a walkthrough like this in about a minute.
Explain your code
Embed this explainer
Drop the interactive walkthrough into a blog or docs. Views never cost a credit.
<iframe src="https://highlit.co/explainers/streaming-a-csv-export-in-spring-explained-java-c87c/embed?autoplay=1" width="100%" height="520" loading="lazy" style="border:0"></iframe>
Autoplay is on by default — add ?autoplay=0 to start paused.