Как вернуть файл для скачивания в Spring MVC контроллере?

«Как вернуть файл для скачивания в Spring MVC контроллере?» — вопрос из категории Spring, который задают на 10% собеседований Java Разработчик. Ниже — развёрнутый ответ с разбором ключевых моментов.

Ответ

В Spring MVC для возврата файла используется ResponseEntity в комбинации с Resource. Это даёт полный контроль над HTTP-заголовками ответа.

Стандартный подход с ResponseEntity:

import org.springframework.core.io.InputStreamResource;
import org.springframework.core.io.Resource;
import org.springframework.http.*;
import org.springframework.web.bind.annotation.*;

@RestController
public class FileController {

    @GetMapping("/download")
    public ResponseEntity<Resource> downloadFile() throws IOException {
        // 1. Загружаем файл в Resource
        File file = new File("/path/to/report.pdf");
        InputStreamResource resource = new InputStreamResource(new FileInputStream(file));

        // 2. Формируем ответ с нужными заголовками
        return ResponseEntity.ok()
                .header(HttpHeaders.CONTENT_DISPOSITION,
                        "attachment; filename="" + file.getName() + """) // Указывает браузеру на скачивание
                .contentType(MediaType.APPLICATION_PDF) // Конкретный MIME-тип
                .contentLength(file.length()) // Размер файла для прогресс-бара
                .body(resource);
    }
}

Ключевые моменты:

  • Content-Disposition: attachment — главный заголовок, заставляющий браузер скачать файл, а не открыть его.
  • Content-Type — следует указывать точный MIME-тип (например, application/pdf, image/png). Для бинарных данных можно использовать MediaType.APPLICATION_OCTET_STREAM.
  • Content-Length — необязателен, но помогает браузеру отображать прогресс загрузки.
  • Типы Resource: Вместо InputStreamResource можно использовать FileSystemResource, ClassPathResource или ByteArrayResource в зависимости от источника файла.

Альтернатива с @ResponseBody (менее гибкая):

@GetMapping(value = "/download", produces = MediaType.APPLICATION_OCTET_STREAM_VALUE)
public @ResponseBody Resource downloadFile() {
    return new FileSystemResource("file.txt");
}