スポンサーリンク

Javaでファイルアップロードする方法|multipart/form-dataをやさしく解説

スポンサーリンク
この記事は約16分で読めます。
スポンサーリンク

JavaでAPIにファイルを送る処理を作ろうとすると、よく出てくる言葉が multipart/form-data です。
ですが「POSTで送ればいいんじゃないの?」「boundaryってなに?」と、ここで止まってしまう人がとても多いです。

たとえば、次のようなAPIはよくあります。

  • 画像アップロードAPI
  • CSVインポートAPI
  • プロフィール画像更新API

こうしたAPIでは、文字データ(usernameなど)とファイルを一緒に送る必要があります。
そのときに使われるのが multipart/form-data という特別なPOST形式です。

この記事では、Java初心者の方でも理解できるように

  • multipart/form-dataの仕組み
  • Javaでファイルをアップロードする流れ
  • そのまま動かせるサンプルコード
  • よくあるエラーの原因

できるだけやさしく解説していきます。

筆者
筆者

「なんとなくコピペで動かす」ではなく、仕組みまでしっかり理解できるようになりますので、ぜひ最後まで読んでみてください。


スポンサーリンク

multipart/form-dataとは?初心者でもわかる基本

ファイルアップロードで使われるPOST形式

multipart/form-dataは、ファイルをサーバーに送るときに使うPOST形式です。

普通のPOSTは、主に次のような文字データを送るために使われます。

username=taro
age=20

ですが、ファイルは少し特別です。

  • 画像
  • CSV
  • PDF

などは、文字ではなくバイナリデータだからです。

そのためHTTPでは、ファイル送信専用の形式として
multipart/form-data が用意されています。

イメージとしては、データをいくつかのパーツに分けて送る方式です。


普通のPOST送信との違い

普通のPOST送信は、データが1つのかたまりで送られます。

例:

username=taro&age=20

一方、multipart/form-dataでは、データをパーツごとに分けて送ります。

username
file
message

つまり

  • テキスト
  • ファイル

1つのリクエストの中にまとめて送れる仕組みです。


boundaryは「データの区切り線」

multipart/form-dataで大事なものが boundary(バウンダリ)です。

これは簡単に言うと、
データとデータの区切り線です。

たとえば、お弁当を想像してみてください。

お弁当には仕切りがありますよね。

[ごはん][唐揚げ][サラダ]

この「仕切り」の役目をするのが boundary です。

実際のHTTPリクエストは、こんなイメージになります。

--boundary

Content-Disposition: form-data; name="username"

taro

--boundary

Content-Disposition: form-data; name="file"; filename="sample.csv"

(ファイルデータ)

--boundary--

ポイントはこの3つです。

  • --boundary でパーツ開始
  • ヘッダーを書く
  • データを書く

そして最後に

--boundary--

で終わります。

筆者
筆者

この仕組みを理解しておくと、Javaでの実装がかなり理解しやすくなります。


Javaでファイルアップロードする流れ

Javaでmultipart/form-dataを送るときは、だいたい次の流れになります。

1つずつ見ていきましょう。


URLとPOSTリクエストを準備する

まずは、送信先のAPI URLを用意して、
POSTリクエストを作ります。

Javaではよく HttpURLConnection を使います。

流れはこんな感じです。

  • URL作成
  • 接続作成
  • POST指定

イメージコードです。

URL url = new URL(apiUrl);
HttpURLConnection conn = (HttpURLConnection) url.openConnection();

conn.setRequestMethod("POST");
conn.setDoOutput(true);

これで「POSTで送る準備」ができました。


Content-Typeをmultipart/form-dataにする

次に、HTTPヘッダーを設定します。

ここがとても重要です。

Content-Type: multipart/form-data

さらに boundary も指定します。

例:

Content-Type: multipart/form-data; boundary=----WebKitFormBoundary123

Javaではこう書きます。

conn.setRequestProperty(
    "Content-Type",
    "multipart/form-data; boundary=" + boundary
);

この設定を忘れると、サーバーはファイル送信だと認識してくれません。


テキストとファイルを送る

次に OutputStream を使ってデータを書き込みます。

順番はこうです。

  • boundaryを書く
  • ヘッダーを書く
  • データを書く

テキスト送信のイメージです。

--boundary
Content-Disposition: form-data; name="username"

taro

ファイルの場合はこうなります。

--boundary
Content-Disposition: form-data; name="file"; filename="sample.csv"
Content-Type: text/csv

(ファイルデータ)

終端データを送る

最後に、multipartの終了を送ります。

--boundary--

この「–」付きのboundaryが終端です。

これを書かないと、サーバーは

「まだデータが続くのかな?」

と判断してしまいます。

その結果

  • 400エラー
  • ファイル未送信

になることがあります。


Javaでmultipart/form-dataを送るサンプルコード

最小構成のサンプルコード

次は、実際に動く最小コードを紹介します。

import java.io.*;
import java.net.HttpURLConnection;
import java.net.URL;
import java.nio.file.Files;

public class MultipartUpload {

    public static void main(String[] args) throws Exception {

        String boundary = "----JavaBoundary";
        File file = new File("sample.txt");

        URL url = new URL("https://example.com/upload");
        HttpURLConnection conn =
                (HttpURLConnection) url.openConnection();

        conn.setRequestMethod("POST");
        conn.setDoOutput(true);

        conn.setRequestProperty(
            "Content-Type",
            "multipart/form-data; boundary=" + boundary
        );

        OutputStream output = conn.getOutputStream();
        PrintWriter writer =
                new PrintWriter(new OutputStreamWriter(output, "UTF-8"), true);

        writer.append("--" + boundary).append("\r\n");
        writer.append(
            "Content-Disposition: form-data; name=\"file\"; filename=\"sample.txt\""
        ).append("\r\n");

        writer.append("Content-Type: text/plain").append("\r\n");
        writer.append("\r\n").flush();

        Files.copy(file.toPath(), output);
        output.flush();

        writer.append("\r\n");
        writer.append("--" + boundary + "--").append("\r\n");
        writer.close();

        int responseCode = conn.getResponseCode();
        System.out.println(responseCode);
    }
}

このコードだけでも、ファイルアップロードAPIに送信できます。


usernameとfileを一緒に送る例

よくあるパターンは

  • username
  • file

を一緒に送るケースです。

--boundary
Content-Disposition: form-data; name="username"

taro

--boundary
Content-Disposition: form-data; name="file"; filename="test.csv"

つまり

テキスト → ファイル

の順番でパーツを書けばOKです。


画像ファイルやCSVを送る例

Content-Typeを変更すれば、いろいろなファイルを送れます。

画像の場合:

Content-Type: image/png

CSVの場合:

Content-Type: text/csv

よくあるアップロード例は次のとおりです。

ファイルContent-Type
PNGimage/png
JPGimage/jpeg
CSVtext/csv
PDFapplication/pdf

コードのやさしい解説

boundaryの役割

boundaryは、データの区切り線です。

コードではここです。

String boundary = "----JavaBoundary";

そして実際の送信では

--boundary

として使います。


ヘッダーの意味

この行はとても重要です。

Content-Disposition: form-data

これは

「フォームデータですよ」

という意味です。

さらに

name="file"

パラメータ名

filename="test.csv"

ファイル名 を指定しています。


ファイル読み込み処理

ファイル送信の部分はここです。

Files.copy(file.toPath(), output);

これは

「ファイルの中身をそのままHTTPに書き込む」

処理です。


レスポンス確認

最後にレスポンスを確認します。

int responseCode = conn.getResponseCode();

成功なら多くの場合

200

が返ります。


よくあるエラーと原因

400 Bad Request

原因で多いのはこの2つです。

  • boundary不一致
  • 終端不足

特に多いのが

--boundary--

を書いていないケースです。


415 Unsupported Media Type

このエラーは

Content-Type

が間違っている場合に起きます。

multipart送信なら必ず

multipart/form-data

を指定します。


ファイルが空になる問題

原因の多くは

  • flush忘れ
  • close忘れ

です。

output.flush();
writer.close();

は忘れないようにしてください。


文字化けの原因

テキスト送信では

UTF-8

を使うようにしましょう。

new OutputStreamWriter(output, "UTF-8")

これを忘れると、日本語が壊れることがあります。


Apache HttpClientで簡単にアップロードする方法

ライブラリを使うメリット

実は、multipart/form-dataは
自分で書くとかなり面倒です。

そこでよく使われるのが Apache HttpClient です。

メリットはこちらです。

  • boundaryを自動生成
  • ヘッダー自動設定
  • コードが短い

つまり ミスが減ります。


HttpClientサンプルコード

とてもシンプルです。

CloseableHttpClient client = HttpClients.createDefault();

HttpPost post = new HttpPost("https://example.com/upload");

MultipartEntityBuilder builder = MultipartEntityBuilder.create();

builder.addTextBody("username", "taro");
builder.addBinaryBody("file", new File("test.csv"));

post.setEntity(builder.build());

CloseableHttpResponse response = client.execute(post);

HttpURLConnectionよりも、かなりコードが短くなります。


うまくいかないときのチェックリスト

アップロードが失敗するときは、次を確認してください。

チェックポイントはこちらです。

  • Content-Typeはmultipart/form-dataか
  • boundaryは一致しているか
  • 改行は \r\n
  • filenameは指定されているか
  • API仕様は合っているか

特に多いのが改行コードミスです。

multipartでは

\r\n

を使う必要があります。


まとめ

今回は Javaでmultipart/form-dataを使ったファイルアップロード方法を解説しました。

ポイントを整理するとこちらです。

  • multipart/form-dataはファイル送信用POST
  • boundaryはデータの区切り線
  • JavaではOutputStreamでデータを書く
  • 終端 --boundary-- を忘れない

まずはこの記事の 最小サンプルコードを実行してみてください。

慣れてきたら、

  • Apache HttpClient
  • Spring RestTemplate
  • WebClient

などのライブラリを使うと、さらに簡単に実装できます。

ぜひ実際にコードを動かして、Javaのファイルアップロードを体験してみてください。

よくある質問

Q
multipart/form-dataとは何ですか?
A

multipart/form-dataとは、HTTPのPOSTリクエストでファイルとテキストを一緒に送るためのデータ形式です。

通常のPOST(application/x-www-form-urlencoded)は文字列データを送るための形式ですが、ファイルを送る場合はデータを複数のパートに分ける必要があります。

そのため multipart/form-data では、以下のようにデータを区切って送信します。

------boundary
Content-Disposition: form-data; name="username"taro
------boundary
Content-Disposition: form-data; name="file"; filename="sample.jpg"
Content-Type: image/jpeg(ファイルデータ)
------boundary--

この boundary(境界線) によって、テキストとファイルのデータを区切っています。

Q
普通のPOST送信との違いは何ですか?
A

通常のPOST送信では、Content-Typeは次の形式になります。

application/x-www-form-urlencoded

例えば次のようなデータです。

username=taro&age=20

しかしファイルを送る場合は、バイナリデータを扱う必要があるため、次の形式を使います。

multipart/form-data

この形式では、データを複数のブロック(パート)に分けて送信する仕組みになっています。

Q
boundaryとは何ですか?
A

boundary(バウンダリ)とは、multipart/form-dataのデータを区切るための文字列です。

例:

Content-Type: multipart/form-data; boundary=----WebKitFormBoundaryABC123

この場合、リクエストボディの中では次のように使われます。

------WebKitFormBoundaryABC123
データ
------WebKitFormBoundaryABC123
データ

イメージとしては、お弁当の仕切りのようなものです。
この区切りがあることで、サーバーは「どこからどこまでが1つのデータか」を理解できます。

Q
Javaでファイルアップロードするときによくあるエラーは?
A

Javaのmultipart実装でよくあるエラーは次の4つです。

① 400 Bad Request

原因

  • boundaryが一致していない
  • 改行コード(CRLF)が不足

対策

  • \r\n を正しく入れる
  • Content-Typeのboundaryと本文のboundaryを一致させる

② 415 Unsupported Media Type

原因

  • Content-Typeが間違っている

対策

Content-Type: multipart/form-data; boundary=XXXX

を設定する。


③ ファイルが空になる

原因

  • OutputStreamに書き込めていない
  • flush / closeしていない

④ 日本語が文字化けする

原因

  • charset未指定

対策

Content-Type: text/plain; charset=UTF-8
Q
HttpURLConnectionとApache HttpClientはどちらを使うべきですか?
A

結論から言うと、実務ではApache HttpClientを使う方が簡単です。

理由は次の通りです。

項目HttpURLConnectionApache HttpClient
コード量多い少ない
boundary処理手動自動
実装難易度高い低い
学習用
実務

HttpURLConnectionは仕組み理解には良いですが、
実際の開発では HttpClientのMultipartEntityBuilder を使うと簡単に実装できます。

タイトルとURLをコピーしました