ファイルを選択
  1. base64hub
  2. 各言語での Base64
  3. Java
コード

Java での Base64

Java 8 以降、java.util.Base64 が標準、URLセーフ、MIME の各 Base64 をストリーム処理も含めて扱えます。以下の出力はすべて Java 21 のものです。

文字列のエンコードとデコード

java.util.Base64 はバイト列を扱います。文字列は文字セットを明示して変換してください。引数なしの getBytes() はプラットフォームのデフォルトを使い、Java 18 より前は必ずしも UTF-8 ではなかったからです。

import java.nio.charset.StandardCharsets;
import java.util.Base64;

String enc = Base64.getEncoder()
        .encodeToString("héllo wörld".getBytes(StandardCharsets.UTF_8));
System.out.println(enc);   // aMOpbGxvIHfDtnJsZA==

String dec = new String(Base64.getDecoder().decode(enc), StandardCharsets.UTF_8);
System.out.println(dec);   // héllo wörld

Base64.Encoder と Base64.Decoder のインスタンスはスレッドセーフなので、static final フィールドに保持しておけます。

パディングあり・なしの URLセーフ Base64

getUrlEncoder() は - と _ を使います。パディングはデフォルトで有効で、withoutPadding() はパディングを省く新しいエンコーダーを返します。

byte[] d = "<<???>>".getBytes(StandardCharsets.UTF_8);

Base64.getUrlEncoder().encodeToString(d);                   // PDw_Pz8-Pg==
Base64.getUrlEncoder().withoutPadding().encodeToString(d);  // PDw_Pz8-Pg

new String(Base64.getUrlDecoder().decode("PDw_Pz8-Pg"), StandardCharsets.UTF_8);
// <<???>>

デコーダーはパディングの有無にかかわらず受け付けるので、getDecoder().decode("aGk") も動作し、hi を返します。JWT のセグメントはパディングなしの Base64URL です。どちらを使うべきかは Base64 と Base64URL の違い をご覧ください。

MIME エンコーダーとデコーダー

getMimeEncoder() は、RFC 2045 の要求どおり、76 文字の行を \r\n で区切って出力します。最後の行の後には改行を付けません。

String mime = Base64.getMimeEncoder().encodeToString("x".repeat(60).getBytes());
// eHh4eHh4...eHh4  (76 chars) + "\r\n" + "eHh4"

Base64.getMimeEncoder(64, "\n".getBytes());   // PEM-style 64-char lines

行の長さは 4 の倍数に切り捨てられます。寛容なのは getMimeDecoder() で、行区切りやアルファベット外の文字をすべて無視します。

Base64.getMimeDecoder().decode("aGVs\r\nbG8K!");   // bytes of "hello\n"

MIME Base64 エンコーダー なら、同じ形式をブラウザで生成できます。

ファイルのエンコードとデコード

小さなファイルの場合:

import java.nio.file.*;

byte[] file = Files.readAllBytes(Path.of("hello.txt"));
System.out.println(Base64.getEncoder().encodeToString(file));   // aGVsbG8K

Files.write(Path.of("copy.txt"), Base64.getDecoder().decode("aGVsbG8K"));

wrap() で大きなファイルをストリーム処理する

Encoder.wrap(OutputStream) と Decoder.wrap(InputStream) はその場でエンコード・デコードを行うので、ファイルがどれだけ大きくてもメモリ使用量は一定です。

try (InputStream in = new FileInputStream("big.bin");
     OutputStream out = Base64.getEncoder().wrap(new FileOutputStream("big.b64"))) {
    in.transferTo(out);
}

try (InputStream in = Base64.getDecoder().wrap(new FileInputStream("big.b64"));
     OutputStream out = new FileOutputStream("big.out")) {
    in.transferTo(out);
}

ラップしたストリームを閉じることが重要です。エンコーダーは close() の時点で最後のグループとパディングを書き出します。忘れると、パディングを含む最大 4 文字の最後のグループが丸ごと欠けます。300 KB のテストファイルでは、出力は encodeToString と完全に一致し、往復変換の結果もバイト単位で同一でした。InputStream.transferTo は Java 9 が必要です。Java 8 ではバッファを使ったループでコピーしてください。

検証と例外

基本のデコーダーと URL デコーダーは厳格で、java.lang.IllegalArgumentException をスローします。メッセージには何が問題だったかが示され、文字は 16進数で表示されます。

getDecoder() への入力メッセージ
PDw_Pz8-Pg==Illegal base64 character 5f
"aGVs\nbG8K"Illegal base64 character a
aGk=!!Input byte array has incorrect ending byte at 4
aGk==Input byte array has incorrect ending byte at 4
aInput byte[] should at least have 2 bytes for base64 bytes

そのため、検証は try/catch だけで済みます。

static boolean isBase64(String s) {
    try {
        Base64.getDecoder().decode(s);
        return true;
    } catch (IllegalArgumentException e) {
        return false;
    }
}

1 つ抜け穴があります。デコーダーは 0 でないパディングビットを受け付けるため、aGl= も何の警告もなく hi にデコードされます。正規形の入力だけを受け付けたいなら、再エンコードして比較してください。Base64 バリデーター はどの文字が問題かを示し、よくある Base64 のエラー では失敗の種類ごとに説明しています。

よくある落とし穴

  • プラットフォームの文字セット。 "text".getBytes() と new String(bytes) はデフォルトの文字セットを使います。Java 17 以前の Windows ではたいてい Windows-1252 で、非 ASCII テキストが文字化けしていました。Java 18 で UTF-8 がデフォルトになりましたが(JEP 400)、StandardCharsets.UTF_8 を渡すのにコストはかかりません。
  • 入力に合わないデコーダー。 URLセーフな入力を基本のデコーダーに渡すと Illegal base64 character 5f か 2d に、MIME の入力なら Illegal base64 character a か d になります。
  • encode() と encodeToString() の違い。 encode() は ASCII 文字の byte[] を返します。その配列を出力すると、Base64 ではなく [B@1b6d3586 のようなものが表示されます。
  • ラップしたストリームを閉じない。 close() しないと、エンコーダーの出力が途中で切れます。
  • Base64 でデータが隠せると思い込む。 隠せません。エンコードと暗号化の違い をご覧ください。

バージョンごとの注意

  • Java 8:java.util.Base64 が導入。基本、URL、MIME の各バリアント、withoutPadding()、wrap() を含む
  • Java 9:上でストリームのコピーに使った InputStream.transferTo
  • Java 11:JAXB が JDK から削除され、javax.xml.bind.DatatypeConverter.printBase64Binary も消滅
  • Java 18:UTF-8 がデフォルトの文字セットに

Kotlin では上記のすべてを使えるうえ、独自のマルチプラットフォーム API もあります。Kotlin での Base64 で解説しています。

よくある質問

「Illegal base64 character 5f」とはどういう意味ですか?

数値は問題の文字を 16進数で表したものです。5f は _、2d は - で、URLセーフな Base64 を getDecoder() でデコードしていることを意味します。代わりに getUrlDecoder() を使ってください。a は改行、20 はスペースです。その場合は getMimeDecoder() を使うか、空白を取り除いてください。

sun.misc.BASE64Encoder や javax.xml.bind.DatatypeConverter はまだ使うべきですか?

いいえ。sun.misc.BASE64Encoder は最近の JDK からは消えた内部 API で、javax.xml.bind は Java 11 で JDK から削除されました。どちらも java.util.Base64 で置き換えられます。

Java のデコーダーはパディングのない Base64 を受け付けますか?

はい。Base64.getDecoder().decode("aGk") は hi のバイト列を返します。デコーダーはパディングの不足は受け付けますが、誤ったパディングは拒否します。

Android で Base64 エンコードするには?

API レベル 26 以降なら java.util.Base64 が使えます。それより古いコードでは android.util.Base64.encodeToString(bytes, Base64.NO_WRAP) を使います。NO_WRAP を付けないと改行が入ります。

続けて読む