Downloading Files

Serving a Stored File

@GetMapping("/api/files/products/{productId}/{name}")
public ResponseEntity<Resource> image(@PathVariable Long productId, @PathVariable String name) throws IOException {
    Path dir = uploadDir.resolve("products").resolve(productId.toString()).normalize();
    Path file = dir.resolve(name).normalize();
    if (!file.startsWith(dir) || !Files.exists(file)) {
        return ResponseEntity.notFound().build();
    }
    String type = Files.probeContentType(file);
    return ResponseEntity.ok()
            .contentType(MediaType.parseMediaType(type != null ? type : "application/octet-stream"))
            .cacheControl(CacheControl.maxAge(Duration.ofDays(30)).cachePublic())
            .body(new FileSystemResource(file));
}

Generated Downloads (Invoices, CSV)

@GetMapping("/api/orders/{id}/invoice")
public ResponseEntity<byte[]> invoice(@PathVariable Long id) {
    byte[] pdf = invoiceService.renderPdf(id);
    return ResponseEntity.ok()
            .header(HttpHeaders.CONTENT_DISPOSITION,
                    ContentDisposition.attachment().filename("INV-" + id + ".pdf").build().toString())
            .contentType(MediaType.APPLICATION_PDF)
            .body(pdf);
}

@GetMapping(value = "/api/admin/orders/export", produces = "text/csv")
public ResponseEntity<StreamingResponseBody> exportCsv() {
    StreamingResponseBody body = out -> {
        try (var writer = new PrintWriter(new OutputStreamWriter(out, StandardCharsets.UTF_8))) {
            writer.println("orderId,userId,total,status");
            orderService.streamAll(o -> writer.printf("%d,%d,%s,%s%n", o.id(), o.userId(), o.total(), o.status()));
        }
    };
    return ResponseEntity.ok()
            .header(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=\"orders.csv\"")
            .body(body);
}

Example

curl -OJ localhost:8080/api/orders/1001/invoice

Example output

curl: Saved to filename 'INV-1001.pdf'
HeaderMeaning
Content-Disposition: inlineshow in the browser (images, PDFs preview)
Content-Disposition: attachment; filename="x"force a download with that name
Content-Typewhat the bytes are (application/pdf, text/csv)

Practice

Exercise: Return a product catalogue as a downloadable CSV named products.csv from GET /api/products/export, using a byte[] body.

Show answer
@GetMapping("/api/products/export")
public ResponseEntity<byte[]> export() {
    StringBuilder sb = new StringBuilder("id,name,price,stock").append(System.lineSeparator());
    productService.findAll().forEach(p ->
        sb.append(p.id()).append(',').append(p.name()).append(',')
          .append(p.price()).append(',').append(p.stock()).append(System.lineSeparator()));
    return ResponseEntity.ok()
        .header(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=\"products.csv\"")
        .contentType(MediaType.parseMediaType("text/csv"))
        .body(sb.toString().getBytes(StandardCharsets.UTF_8));
}