Base64デコードは「文字列を元の世界に連れ戻す」技
Base64エンコードが「バイナリを文字列の世界に連れてくる」技だとしたら、
Base64デコードはその逆で、「Base64文字列から、元のバイト列や文字列の世界に戻す」技です。
ここを雑にやると、「文字化けする」「バイト列の長さが合わない」「そもそも Base64 じゃない文字列で落ちる」といった事故が起きます。
だからこそ、“どこからでも同じルールで Base64 を戻せるユーティリティ” を一つ持っておくと、コードがかなり安定します。
基本の考え方:「バイト列に戻してから、必要なら文字列にする」
Base64 はあくまで「バイト列 ↔ 文字列」の橋渡し
Base64 は、「バイト列」を「ASCIIだけで構成された文字列」に変換する仕組みです。
つまり、デコードするときの本質は「Base64文字列 → バイト列」に戻すことです。
そこから先は用途次第です。
バイナリファイルに戻したいなら、そのバイト列をファイルに書き出す。
テキストに戻したいなら、そのバイト列を「正しい文字コード」で文字列に変換する。
この「まずバイト列に戻す → そこからどう使うかは目的次第」という流れを意識しておくと、迷いにくくなります。
Java標準の Base64 デコードユーティリティを押さえる
java.util.Base64.Decoder をラップする
Java 8 以降では、java.util.Base64 にデコード用の Decoder が用意されています。
これをそのまま使ってもいいのですが、エンコードと同じく「入口を一箇所にまとめる」ためにユーティリティ化しておきます。
import java.nio.charset.StandardCharsets;
import java.util.Base64;
public final class Base64s {
private Base64s() {}
public static byte[] decode(String base64) {
if (base64 == null) {
return null;
}
return Base64.getDecoder().decode(base64);
}
public static String decodeUtf8(String base64) {
byte[] bytes = decode(base64);
if (bytes == null) {
return null;
}
return new String(bytes, StandardCharsets.UTF_8);
}
}
Javaここで深掘りしたい重要ポイントは二つです。
一つ目は、「Base64デコードの“本体”は byte[] を返すメソッドにしておく」ことです。decode は「Base64文字列 → byte[]」だけを担当し、decodeUtf8 は「その byte[] を UTF-8 文字列として解釈する」という“用途別のラッパー”になっています。
二つ目は、「文字コードを必ず明示する」ことです。new String(bytes) と引数なしで書くと、環境依存のデフォルトエンコーディングになってしまいます。
エンコード側で UTF-8 を使っているなら、デコード側も必ず UTF-8 を指定してペアにしておく必要があります。
例題:文字列を Base64 → 元の文字列に戻す
エンコードとデコードがペアになっていることを確認する
まずは、前回の Base64エンコードとセットで「往復」させてみます。
public class Base64RoundTripExample {
public static void main(String[] args) {
String original = "こんにちは、Base64!";
String encoded = Base64s.encodeUtf8(original);
String decoded = Base64s.decodeUtf8(encoded);
System.out.println("original = " + original);
System.out.println("encoded = " + encoded);
System.out.println("decoded = " + decoded);
}
}
Java期待するのは、「original と decoded が完全に一致する」ことです。
ここでの重要ポイントは、「エンコード側とデコード側で“文字コードと方式がペアになっている”ことを確認する」ことです。
エンコード時に UTF-8 でバイト列にしているなら、デコード時も UTF-8 で文字列に戻す。
標準 Base64 を使っているなら、デコード側も標準 Base64 の getDecoder() を使う。
このペアが崩れると、文字化けやデコードエラーの原因になります。
例題:Base64文字列からファイルを復元する
JSONや設定に埋め込んだバイナリを元に戻す
よくあるパターンとして、「画像ファイルを Base64 にして JSON に載せたものを、サーバ側で受け取ってファイルに戻す」というケースがあります。
例えば、クライアントからこんな JSON が来たとします。
{
"fileName": "logo.png",
"contentBase64": "iVBORw0KGgoAAAANSUhEUgAA..."
}
これをファイルに復元するコードはこう書けます。
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
public final class FileBase64 {
private FileBase64() {}
public static void decodeToFile(String base64, Path outputPath) throws IOException {
byte[] bytes = Base64s.decode(base64);
Files.write(outputPath, bytes);
}
}
Java使い方はこうです。
String base64 = /* JSON から取り出した contentBase64 */;
Path output = Path.of("restored-logo.png");
FileBase64.decodeToFile(base64, output);
Javaここで深掘りしたいのは、「Base64デコードの結果は“そのままファイルに書ける生のバイト列”である」という感覚です。
画像でも PDF でも ZIP でも、元が何であれ、Base64 から戻ってくるのは「元のバイト列そのもの」です。
つまり、「Base64 → byte[] → Files.write」で、ほぼそのまま復元できる、というシンプルさを覚えておくと使いやすくなります。
URLセーフ Base64 のデコード
エンコード時と同じ「種類」でデコードする
URL やトークンで使う「URLセーフ Base64」は、+ や / の代わりに - と _ を使い、パディング = を省略することが多い形式です。
エンコード時に Base64.getUrlEncoder() を使ったなら、デコード時も getUrlDecoder() を使う必要があります。
import java.util.Base64;
public final class Base64s {
private Base64s() {}
public static byte[] decodeUrlSafe(String base64) {
if (base64 == null) {
return null;
}
return Base64.getUrlDecoder().decode(base64);
}
}
Java例えば、トークンを復元するコードはこうです。
String token = TokenGenerator.randomToken(32); // URLセーフ Base64 で生成したとする
byte[] raw = Base64s.decodeUrlSafe(token);
System.out.println("raw length = " + raw.length);
Javaここでの重要ポイントは、「“標準 Base64”と“URLセーフ Base64”は互換ではないので、必ずペアで使う」ことです。getEncoder() でエンコードしたものは getDecoder() で、getUrlEncoder() でエンコードしたものは getUrlDecoder() で戻す。
ここを混ぜると、IllegalArgumentException: Illegal base64 character のような例外が飛びます。
Base64デコードで気をつけたいエラーと設計
不正な Base64 文字列が来たときどうするか
Base64.getDecoder().decode(...) は、不正な文字列が渡されると IllegalArgumentException を投げます。
例えば、Base64 に使われない文字(日本語やスペースなど)が混ざっている場合です。
ユーティリティ側でそのまま例外を投げる設計にしておくと、呼び出し側で「入力が壊れている」ことに気づきやすくなります。
public static byte[] decode(String base64) {
if (base64 == null) {
return null;
}
try {
return Base64.getDecoder().decode(base64);
} catch (IllegalArgumentException e) {
throw new IllegalArgumentException("不正な Base64 文字列です: " + base64, e);
}
}
Java呼び出し側では、例えばこう扱います。
try {
byte[] data = Base64s.decode(inputBase64);
// 正常処理
} catch (IllegalArgumentException e) {
// 400 Bad Request を返す、ログに入力を出す(マスクしつつ)、など
}
Javaここでのポイントは、「“壊れた Base64”を無理やり扱おうとしない」ことです。
「とりあえず null を返す」「空配列を返す」といった実装にすると、
どこで入力が壊れているのか分からなくなり、調査がつらくなります。
Base64デコードと文字コードのペアリング
「エンコード時の文字コード」を必ず意識する
文字列を Base64 にしていた場合、デコードして文字列に戻すときには、
「エンコード時にどの文字コードでバイト列にしたか」を必ず思い出す必要があります。
例えば、エンコード側がこうだったとします。
byte[] bytes = text.getBytes(StandardCharsets.UTF_8);
String base64 = Base64.getEncoder().encodeToString(bytes);
Javaこの場合、デコード側は必ずこうでなければいけません。
byte[] bytes = Base64.getDecoder().decode(base64);
String text = new String(bytes, StandardCharsets.UTF_8);
Javaここで new String(bytes) としてしまうと、環境によっては Shift_JIS などで解釈され、文字化けします。
つまり、「Base64 は“バイト列をそのまま保存しているだけ”なので、文字列に戻すときの文字コードは自分で合わせる必要がある」という感覚が大事です。
まとめ:Base64デコードユーティリティで身につけたい感覚
Base64デコードは、「よく分からない英数字の塊」を、元の世界(バイト列・文字列・ファイル)に連れ戻す技です。
押さえておきたい感覚は、まず「Base64デコードの本体は“Base64文字列 → byte[]”であり、そこから先は用途ごとにラッパーを用意する」こと。
次に、「標準 Base64 と URLセーフ Base64 をペアで使い、エンコード時と同じ種類・同じ文字コードでデコードする」こと。
そして、「不正な Base64 文字列は例外として扱い、“入力が壊れている”というシグナルをきちんと呼び出し側に伝える」ことです。
