文字列のエンコードとデコード
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 |
a | Input 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 を付けないと改行が入ります。