はじめに (対象読者・この記事でわかること)

この記事は、Spring BootでREST APIを開発しているが「HTMLファイルをブラウザにダウンロードさせる方法がイマイチ分からない」というJavaエンジニアを対象にしています。
特に、レスポンスヘッダーのContent-Dispositionの扱いや、日本語ファイル名で文字化けしないようにするテクニックに悩んでいる方に最適です。

読み進めることで以下のことができるようになります。

  • Spring BootのResponseEntityを使ったファイルダウンロードAPIの実装
  • 日本語ファイル名でも文字化けしないContent-Dispositionの書き方
  • ファイルが存在しない場合のエラーハンドリングとログ出力
  • ダウンロード時のMIMEタイプ設定とキャッシュ制御

前提知識

この記事を読み進める上で、以下の知識があるとスムーズです。 - Java 11以降の基本的な文法 - Spring Boot 3系のプロジェクト構成(@RestController@GetMappingの基礎) - HTTPレスポンスヘッダーの基礎知識(Content-TypeContent-Dispositionの意味)

なぜ「HTMLファイルのダウンロード」が面倒なのか

Spring BootでJSONを返すREST APIなら@ResponseBody@RestControllerが自動でやってくれるため、ほとんど意識しません。
しかし「ブラウザにファイルをダウンロードさせる」場合は、以下の3点を自前で制御する必要があり、初学者にはハードルが高く感じます。

  1. レスポンスボディにファイルのバイト列を載せる
  2. ブラウザに「ダウンロードして保存させる」ことを指示するHTTPヘッダーを付与する
  3. ファイル名に日本語を含めた際の文字化けを防ぐ

特に2.の「Content-Disposition: attachment; filename*=UTF-8''...」の書き方は、RFC 6266をそのまま実装しようとすると罠が多く、IE対応やfilename*filenameの二重指定など、ググっても情報が錯綜しています。
本記事では、これらを「最新のSpring Boot 3系で動作確認済み」の形で一挙に整理します。

Spring BootでHTMLファイルをダウンロードさせる実装手順

ステップ1: 依存関係とプロジェクト構成を整える

Spring Boot 3.2以降を想定し、追加で特別な依存は不要です。
プロジェクトルートにdownloadフォルダを作り、そこにsample.htmlを配置している前提で進めます。

Java
src ├── main │ ├── java │ │ └── com │ │ └── example │ │ └── demo │ │ ├── DemoApplication.java │ │ └── controller │ │ └── FileDownloadController.java │ └── resources │ ├── application.yml │ └── download │ └── sample.html

application.ymlには、ファイルの格納ディレクトリを外部設定化しておくと運用時に便利です。

Yaml
file: storage: path: ${user.dir}/src/main/resources/download/

ステップ2: コントローラーでダウンロードAPIを実装する

ResponseEntityInputStreamResourceを使うのがSpring Boot流です。
ポイントは、日本語ファイル名を安全に付けるため、UriUtils.encode()でエンコードしてからContent-Dispositionに組み込むことです。

Java
package com.example.demo.controller; import org.springframework.beans.factory.annotation.Value; import org.springframework.core.io.InputStreamResource; import org.springframework.http.HttpHeaders; import org.springframework.http.MediaType; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RestController; import java.io.File; import java.io.FileInputStream; import java.io.FileNotFoundException; @RestController public class FileDownloadController { @Value("${file.storage.path}") private String storagePath; @GetMapping("/download/html/{filename:.+}") public ResponseEntity<InputStreamResource> downloadHtml( @PathVariable String filename) throws FileNotFoundException { File file = new File(storagePath + filename); if (!file.exists()) { throw new FileNotFoundException("ファイルが見つかりません: " + filename); } InputStreamResource resource = new InputStreamResource(new FileInputStream(file)); // 日本語ファイル名をUTF-8でエンコード String encoded = UriUtils.encode(filename, StandardCharsets.UTF_8); return ResponseEntity.ok() .header(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename*=UTF-8''" + encoded) .contentType(MediaType.TEXT_HTML) .contentLength(file.length()) .body(resource); } }

ステップ3: エラーハンドリングとログを整える

ファイルが存在しない場合は、404を返したいので専用の例外ハンドラーを用意します。

Java
@ControllerAdvice public class GlobalExceptionHandler { private static final Logger log = LoggerFactory.getLogger(GlobalExceptionHandler.class); @ExceptionHandler(FileNotFoundException.class) public ResponseEntity<ErrorResponse> handleNotFound(FileNotFoundException ex) { log.warn("ファイルダウンロード失敗: {}", ex.getMessage()); return ResponseEntity .status(HttpStatus.NOT_FOUND) .body(new ErrorResponse("FILE_NOT_FOUND", ex.getMessage())); } }

ハマった点:IE/Edgeで日本語ファイル名が文字化けする

2025年時点でもIEモードを使う顧客システムは尽きません。
filename*=UTF-8''だけでなく、旧来のfilenameも併記してやらないと、IEで「.html」が消えた文字化けファイル名になることがあります。

解決策:フォールバック付きのContent-Disposition

Java
String disposition = String.format( "attachment; filename=\"%s\"; filename*=UTF-8''%s", filename.replace("\"", "\\\""), // クォートエスケープ encoded);

このように、旧形式と新形式を同時に記載してやることで、モダンブラウザもレガシーブラウザも正しくファイル名を認識してくれます。

まとめ

本記事では、Spring BootでHTMLファイルをブラウザにダウンロードさせる実装を解説しました。

  • ResponseEntity<InputStreamResource>を使うと、メモリ効率よくファイルを返せる
  • 日本語ファイル名はUriUtils.encode()してfilename*=UTF-8''にする
  • IE対策としてfilenameも併記してフォールバップする
  • ファイルが存在しない場合は@ControllerAdviceで404を返してあげる

この記事を通して、Spring Bootで「ファイルダウンロードAPI」を実装する際の落とし穴を回避できるようになったはずです。
次回は、Spring Securityを使った「ダウンロード権限の細分化」や、S3からのストリーミングダウンロードについて掘り下げていく予定です。

参考資料