URLデコードは「URLの中身を人間とプログラムの世界に戻す」技
URLエンコードが「暴れそうな文字を URL の中で安全な形に変える」技だとしたら、
URLデコードはその逆で、「%E3%81%82 みたいな記号だらけの文字列を、本来の意味のある文字列に戻す」技です。
ここを雑にやると、「日本語が文字化けする」「+ がスペースになったりならなかったりする」「二重デコードで壊れる」といった、
地味だけど厄介なバグが出ます。だからこそ、“どこからでも同じルールで URL を戻せるユーティリティ” を一つ持っておくと、コードが安定します。
URLデコードのイメージをつかむ
「%XX や + を、元の文字に戻す」
URLエンコードでは、URLの中で問題を起こしそうな文字を %E3%81%82 のような形に変えました。
URLデコードは、その逆変換です。
例えば、次のようなクエリ文字列があったとします。
q=Java+URL+%E3%83%87%E3%82%B3%E3%83%BC%E3%83%89
これを URLデコードすると、q の値はこうなります。
Java URL デコード
ここで大事なのは、「URLデコードは“どの場面でエンコードされたか”とセットで考える必要がある」ということです。
特に、+ をスペースとみなすかどうかは、「フォーム形式(application/x-www-form-urlencoded)かどうか」で変わります。
Java の URLDecoder はフォーム形式前提なので、+ をスペースとして扱います。
Java の URLDecoder をそのまま散らばらせない
URLDecoder の基本と「罠」
Java には java.net.URLDecoder というクラスがあります。
これを使えば簡単に URLデコードできます。
import java.io.UnsupportedEncodingException;
import java.net.URLDecoder;
public class RawUrlDecodeExample {
public static void main(String[] args) throws UnsupportedEncodingException {
String decoded = URLDecoder.decode("Java+URL+%E3%83%87%E3%82%B3%E3%83%BC%E3%83%89", "UTF-8");
System.out.println(decoded); // Java URL デコード
}
}
Java動きは分かりやすいのですが、そのまま業務コードに散らばらせると、次のような問題が出ます。
文字コードを文字列で指定するので、”UTF-8″ のタイプミスがコンパイル時に検出されない。
UTF-8 以外をうっかり使うと文字化けする。UnsupportedEncodingException というチェック例外を毎回ハンドリングしないといけない。
さらに、URLDecoder は「フォーム形式(application/x-www-form-urlencoded)」前提なので、+ をスペースとして扱います。これはクエリパラメータでは一般的に期待どおりですが、
「URL全体」や「パス部分」をデコードしたいときには、意図とズレることがあります。
だからこそ、用途を絞った小さなユーティリティで包んでしまうのが現実的です。
クエリパラメータ用の URLデコードユーティリティを作る
UTF-8 固定・フォーム形式前提の decodeQueryParam
まずは、「クエリパラメータの値をデコードする」ことに特化したユーティリティを作ります。
import java.io.UncheckedIOException;
import java.io.UnsupportedEncodingException;
import java.net.URLDecoder;
import java.nio.charset.StandardCharsets;
public final class UrlDecoders {
private UrlDecoders() {}
public static String decodeQueryParam(String value) {
if (value == null) {
return null;
}
try {
return URLDecoder.decode(value, StandardCharsets.UTF_8.name());
} catch (UnsupportedEncodingException e) {
throw new UncheckedIOException(e);
}
}
}
Javaここで深掘りしたい重要ポイントは三つです。
一つ目は、「UTF-8 を固定している」ことです。
エンコード側(URLEncoder.encode(..., "UTF-8") や StandardCharsets.UTF_8)とペアにするためにも、
デコード側も必ず UTF-8 を明示しておきます。
二つ目は、「null をそのまま null として返す」ことです。
エンコード側で「null は空文字にする」と決めているなら、デコード側もそれに合わせる、というように、
プロジェクトとしての方針をここで決めておくと、呼び出し側が迷いません。
三つ目は、「UnsupportedEncodingException を UncheckedIOException にラップしている」ことです。
UTF-8 は Java 標準で必ずサポートされているので、本来この例外は起きません。
「起きたらプログラミングミス」とみなしてランタイム例外にしてしまう設計がよく使われます。
例題:クエリ文字列からパラメータを取り出してデコードする
name=...&age=... を Map に落とし込む
URLデコードが必要になる典型的な場面が、「クエリ文字列をパースして、パラメータを取り出す」処理です。
例えば、次のようなクエリ文字列があったとします。
name=%E5%B1%B1%E7%94%B0%E5%A4%AA%E9%83%8E&age=30&keyword=Java+URL+%E3%83%87%E3%82%B3%E3%83%BC%E3%83%89
これを Map<String, String> に落とし込むユーティリティを作ってみます。
import java.util.LinkedHashMap;
import java.util.Map;
public final class QueryStringParser {
private QueryStringParser() {}
public static Map<String, String> parse(String query) {
Map<String, String> result = new LinkedHashMap<>();
if (query == null || query.isEmpty()) {
return result;
}
String[] pairs = query.split("&");
for (String pair : pairs) {
int idx = pair.indexOf('=');
String rawName;
String rawValue;
if (idx >= 0) {
rawName = pair.substring(0, idx);
rawValue = pair.substring(idx + 1);
} else {
rawName = pair;
rawValue = "";
}
String name = UrlDecoders.decodeQueryParam(rawName);
String value = UrlDecoders.decodeQueryParam(rawValue);
result.put(name, value);
}
return result;
}
}
Java使い方はこうです。
String query = "name=%E5%B1%B1%E7%94%B0%E5%A4%AA%E9%83%8E&age=30&keyword=Java+URL+%E3%83%87%E3%82%B3%E3%83%BC%E3%83%89";
Map<String, String> params = QueryStringParser.parse(query);
System.out.println(params.get("name")); // 山田太郎
System.out.println(params.get("age")); // 30
System.out.println(params.get("keyword")); // Java URL デコード
Javaここでの重要ポイントは、「“分解(split)”と“デコード”をきちんと分けている」ことです。& と = でクエリ文字列を分解したあと、キーと値をそれぞれ decodeQueryParam に通しています。
これにより、「構造としてのクエリパラメータ」と「エンコードされた値」をきれいに分離して扱えるようになります。
例題:リダイレクトURLの中の「戻り先URL」を取り出す
URLの中に入っている URL をデコードする
URLエンコードのときにやった、「ログイン画面にリダイレクトするとき、戻り先URLをクエリパラメータに載せる」パターンを、今度は逆側から見てみます。
例えば、こんな URL が来たとします。
https://example.com/login?redirect=https%3A%2F%2Fexample.com%2Fapp%2Fhome%3Ftab%3Dprofile
この中から redirect の値を取り出し、元の URL に戻したいとします。
String url = "https://example.com/login?redirect=https%3A%2F%2Fexample.com%2Fapp%2Fhome%3Ftab%3Dprofile";
String query = url.substring(url.indexOf('?') + 1); // login? の後ろだけを取り出す
Map<String, String> params = QueryStringParser.parse(query);
String redirect = params.get("redirect");
System.out.println(redirect); // https://example.com/app/home?tab=profile
Javaここで深掘りしたいのは、「“URL全体”をデコードしているのではなく、“クエリパラメータの値としての URL”をデコードしている」という点です。
外側の ? や & は構造を表す記号なのでそのまま、
内側の URL は「値」として扱うので、: や /、?、= を含めてエンコード・デコードしています。
この「どこまでが構造で、どこからが値か」を意識できるようになると、URLデコードの使いどころが一気にクリアになります。
URLデコードで気をつけたいポイント
エンコード時と「文字コード」と「方式」を合わせる
URLデコードで一番大事なのは、「エンコード時と同じ前提でデコードする」ことです。
エンコード側がこうだったなら、
String encoded = URLEncoder.encode(value, "UTF-8");
Javaデコード側は必ずこうでなければいけません。
String decoded = URLDecoder.decode(encoded, "UTF-8");
Javaここで、片方だけ Shift_JIS にしたり、片方だけ URLDecoder を使わずに生の文字列として扱ったりすると、
文字化けやデコードエラーの原因になります。
また、「フォーム形式(+ をスペースとみなす)」前提でエンコードしているなら、
デコード側も同じ前提(URLDecoder)で戻す必要があります。
二重デコードをしない
よくある失敗が、「すでにデコード済みの文字列を、もう一度デコードしてしまう」パターンです。
例えば、%E3%81%82 を一度デコードして あ にしたあと、
さらに URLDecoder.decode("あ", "UTF-8") を呼んでも、今度は % も何もないので意味がありません。
逆に、「二重エンコード」された文字列(%25E3%2581%2582 のようなもの)を一回だけデコードすると、%E3%81%82 になってしまい、さらにもう一回デコードしないと あ になりません。
「どのタイミングでエンコードしたか」「どのタイミングでデコードするか」を設計として決めておき、
「同じ値を複数回デコードしない」という意識を持つことが大事です。
まとめ:URLデコードユーティリティで身につけたい感覚
URLデコードは、「URLの中で安全な形にされていた値を、人間とプログラムが扱いやすい形に戻す」技です。
押さえておきたい感覚は、まず「URLデコードは“値”に対して行うものであり、URL全体を丸ごとデコードするものではない」こと。
次に、「URLDecoder をそのまま散らばらせず、UTF-8 固定・用途別(クエリ用)のユーティリティに閉じ込める」こと。
そして、「クエリ文字列の分解とデコードをユーティリティに任せることで、& や =、文字化け、二重デコードといった細かいミスを根こそぎ減らす」ことです。
