การถอดรหัส Base64 ใน Java เป็นสิ่งที่ผมต้องใช้แทบทุกสองสามวัน — ไม่ว่าจะดึง secret จาก Kubernetes environment variable อ่าน binary payload จาก REST API หรือตรวจสอบ JWT token ระหว่าง debug session คลาส java.util.Base64 ที่มาพร้อม JDK 8 มี decoder สามแบบ: getDecoder() สำหรับ Base64 มาตรฐาน, getUrlDecoder() สำหรับ input แบบ URL-safe และ getMimeDecoder() สำหรับข้อมูลที่มีการตัดบรรทัด เช่น email attachment หากต้องการตรวจสอบแบบด่วนโดยไม่เขียนโค้ด Base64 decoder ของ ToolDeck จัดการได้ทันทีในเบราว์เซอร์ คู่มือนี้มุ่งเป้าที่ Java 8+ และครอบคลุม decoder ทั้งสามแบบ การ streaming ด้วย wrap(InputStream), การแยก JWT payload, การถอดรหัสไฟล์และ API response, Apache Commons Codec เป็นทางเลือก และข้อผิดพลาดสี่อย่างที่ทำให้ output เสียหายใน production
- ✓Base64.getDecoder().decode(s) คือวิธีมาตรฐาน — มีใน java.util.Base64 ตั้งแต่ JDK 8 ไม่ต้องติดตั้ง dependency เพิ่ม
- ✓ใช้ getUrlDecoder() สำหรับ JWT token และ OAuth payload — ใช้ตัวอักษร - และ _ ไม่ใช่ + และ /
- ✓getMimeDecoder() ละเว้น line break และ whitespace จึงเหมาะกับ email attachment และ PEM certificate
- ✓decoder.wrap(InputStream) ถอดรหัสแบบ on the fly สำหรับไฟล์ขนาดใหญ่โดยไม่โหลดทุกอย่างลงหน่วยความจำ
- ✓basic decoder มีความเข้มงวด — trailing newline, space หรืออักขระ alphabet ผิดประเภทจะ throw IllegalArgumentException ทันที
Base64 Decoding คืออะไร?
การเข้ารหัส Base64 แปลงข้อมูล binary เป็น ASCII ที่ประกอบด้วย 64 อักขระ เพื่อให้ส่งผ่าน ช่องทางที่รองรับเฉพาะข้อความได้อย่างปลอดภัย ไม่ว่าจะเป็น JSON field, HTTP header, XML document หรือ email body การถอดรหัสทำกระบวนการย้อนกลับ: Base64 ทุก 4 อักขระ จะแมปกลับเป็น 3 byte ดั้งเดิม = padding ที่ท้ายบอกจำนวน byte ที่เติมเพื่อให้กลุ่มสุดท้ายครบ Base64 ไม่ใช่การเข้ารหัสลับ — ใครก็ย้อนกลับได้ จุดประสงค์คือความปลอดภัยในการส่งข้อมูล ไม่ใช่การรักษาความลับ
กรณีการถอดรหัสที่พบบ่อยใน Java: ดึงค่า configuration ที่ inject มาเป็น Base64 environment variable, แกะ binary file content จาก cloud API response, อ่าน PEM-encoded certificate และตรวจสอบ JWT token payload ระหว่าง debug
ZGItcHJvZC51cy1lYXN0LTEuYW1hem9uYXdzLmNvbTo1NDMy
db-prod.us-east-1.amazonaws.com:5432
Base64.getDecoder().decode() — วิธีถอดรหัสมาตรฐาน
คลาส java.util.Base64 ถูกเพิ่มใน JDK 8 และแทนที่ sun.misc.BASE64Decoder เก่าที่ทุกคนเคยพึ่งพา ไม่ต้องการ external dependency — แค่ import java.util.Base64 แล้วเรียก Base64.getDecoder().decode() เมธอดรับทั้ง String และ byte[] และคืนค่าเป็น byte[] ของข้อมูลที่ถอดรหัสแล้ว
ตัวอย่างเบื้องต้น
import java.util.Base64;
import java.nio.charset.StandardCharsets;
public class DecodeCredential {
public static void main(String[] args) {
// Kubernetes secret value, Base64-encoded
String encoded = "ZGItcHJvZC51cy1lYXN0LTEuYW1hem9uYXdzLmNvbTo1NDMy";
byte[] decodedBytes = Base64.getDecoder().decode(encoded);
String connectionString = new String(decodedBytes, StandardCharsets.UTF_8);
System.out.println(connectionString);
// db-prod.us-east-1.amazonaws.com:5432
}
}ระบุ StandardCharsets.UTF_8 เสมอเมื่อสร้าง String constructor แบบ new String(bytes) ที่ไม่ระบุ charset ใช้ encoding ค่าเริ่มต้นของ platform ซึ่งแตกต่างกันในแต่ละระบบ บน Windows server ที่ใช้ Cp1252 เป็นค่าเริ่มต้น อักขระ UTF-8 แบบ multi-byte จะเสียหายโดยไม่มีการแจ้งเตือน
การตรวจสอบแบบ round-trip
import java.util.Base64;
import java.nio.charset.StandardCharsets;
public class RoundTrip {
public static void main(String[] args) {
String original = "redis://cache-prod.internal:6379/session-store";
String encoded = Base64.getEncoder().encodeToString(
original.getBytes(StandardCharsets.UTF_8)
);
System.out.println(encoded);
// cmVkaXM6Ly9jYWNoZS1wcm9kLmludGVybmFsOjYzNzkvc2Vzc2lvbi1zdG9yZQ==
byte[] decoded = Base64.getDecoder().decode(encoded);
String recovered = new String(decoded, StandardCharsets.UTF_8);
System.out.println(recovered.equals(original)); // true
}
}การถอดรหัสลง buffer ที่จัดสรรไว้ล่วงหน้า
overload แบบสามอาร์กิวเมนต์ decode(byte[] src, byte[] dst) เขียนผลลัพธ์ลงใน destination buffer โดยตรงและคืนจำนวน byte ที่เขียน ช่วยหลีกเลี่ยง การจัดสรรหน่วยความจำเพิ่มเติมในเส้นทางที่ถูกเรียกบ่อย:
import java.util.Base64;
public class DecodeToBuffer {
public static void main(String[] args) {
byte[] src = "eyJob3N0IjoiMTAuMC4xLjUwIiwicG9ydCI6ODQ0M30=".getBytes();
byte[] dst = new byte[1024]; // pre-allocated
int len = Base64.getDecoder().decode(src, dst);
String result = new String(dst, 0, len);
System.out.println(result);
// {"host":"10.0.1.50","port":8443}
}
}decode() จะ throw IllegalArgumentException หาก input มีอักขระที่ไม่อยู่ใน Base64 alphabet (รวมถึง line break และ space) ถ้า input อาจมี whitespace ให้เปลี่ยนเป็น getMimeDecoder() หรือ ทำ strip ด้วย encoded.strip() ก่อนถอดรหัสการถอดรหัส Base64 กับประเภทข้อมูลที่ไม่ใช่มาตรฐานและ Custom Object
byte[] ดิบที่ได้จาก decode() มักต้องแปลงเป็นสิ่งที่เฉพาะเจาะจงกว่า: UUID, Java object ที่ serialize แล้ว, protobuf message หรือ timestamp decoder เองคืนแค่ byte เสมอ — การแปลงเป็น domain type เป็นหน้าที่ของคุณ
Base64 เป็น UUID
import java.util.Base64;
import java.nio.ByteBuffer;
import java.util.UUID;
public class DecodeUUID {
public static UUID fromBase64(String encoded) {
byte[] bytes = Base64.getUrlDecoder().decode(encoded);
ByteBuffer bb = ByteBuffer.wrap(bytes);
return new UUID(bb.getLong(), bb.getLong());
}
public static void main(String[] args) {
// Compact Base64-encoded UUID from an API response
String encoded = "f47ac10b-58cc-4372-a567-0e02b2c3d479";
UUID original = UUID.fromString(encoded);
// Encode to Base64 (compact form — 22 chars vs 36)
ByteBuffer bb = ByteBuffer.wrap(new byte[16]);
bb.putLong(original.getMostSignificantBits());
bb.putLong(original.getLeastSignificantBits());
String compact = Base64.getUrlEncoder().withoutPadding()
.encodeToString(bb.array());
System.out.println(compact); // 9HrBC1jMQ3KlZw4CssPUeQ
// Decode back
UUID recovered = fromBase64(compact);
System.out.println(recovered); // f47ac10b-58cc-4372-a567-0e02b2c3d479
}
}Base64 เป็น JSON object ที่ deserialize ด้วย Jackson
import java.util.Base64;
import java.nio.charset.StandardCharsets;
import com.fasterxml.jackson.databind.ObjectMapper;
import java.time.Instant;
public class DecodeJsonPayload {
record DeployEvent(String service, String region, Instant deployedAt, int replicas) {}
public static void main(String[] args) throws Exception {
// Base64-encoded JSON payload from a message queue
String encoded = "eyJzZXJ2aWNlIjoicGF5bWVudC1nYXRld2F5Iiwi"
+ "cmVnaW9uIjoiZXUtd2VzdC0xIiwiZGVwbG95ZWRBdCI6"
+ "IjIwMjYtMDMtMTVUMTQ6MzA6MDBaIiwicmVwbGljYXMiOjR9";
byte[] jsonBytes = Base64.getDecoder().decode(encoded);
String json = new String(jsonBytes, StandardCharsets.UTF_8);
System.out.println(json);
// {"service":"payment-gateway","region":"eu-west-1",
// "deployedAt":"2026-03-15T14:30:00Z","replicas":4}
ObjectMapper mapper = new ObjectMapper();
mapper.findAndRegisterModules(); // picks up JavaTimeModule
DeployEvent event = mapper.readValue(jsonBytes, DeployEvent.class);
System.out.println(event.service()); // payment-gateway
System.out.println(event.deployedAt()); // 2026-03-15T14:30:00Z
}
}ObjectInputStream เพื่อ deserialize ข้อมูล Base64 ที่ไม่น่าเชื่อถือ การโจมตีผ่าน Java deserialization มีบันทึกไว้ชัดเจน — ถ้า encoded content มาจากแหล่งภายนอก ให้ parse เป็น JSON หรือ protobuf แทนการใช้ native Java serializationเอกสารอ้างอิงเมธอดของ Base64.Decoder
ทุกเมธอดอยู่ใน java.util.Base64 และ inner class Base64.Decoder factory method สามแบบบน Base64 คืน decoder instance ที่แตกต่างกัน ส่วนเมธอด decode() และ wrap() อยู่บน Decoder instance
getMimeDecoder() — ถอดรหัส Base64 แบบ MIME ที่มีการตัดบรรทัด
basic decoder ปฏิเสธทุกอย่างที่ไม่อยู่ใน Base64 alphabet — รวมถึง line break \r\n ที่ MIME-encoded content มักมีอยู่ Email attachment, PEM certificate และ API response เก่าบางตัวตัดบรรทัด Base64 ทุก 76 อักขระ getMimeDecoder() ละเว้น line separator และอักขระที่ไม่อยู่ใน Base64 alphabet โดยอัตโนมัติ จึงรองรับกรณีนี้ได้ทันที
import java.util.Base64;
import java.nio.charset.StandardCharsets;
public class MimeDecode {
public static void main(String[] args) {
// PEM certificate body — line-wrapped at 76 characters
String pemBody = "LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0t\r\n"
+ "TUlJQm96Q0NBVWlnQXdJQkFnSUpBSXBhVDJU\r\n"
+ "aVFvZU1BMEdDU3FHU0liM0RRRU==";
// getDecoder() would throw IllegalArgumentException here
byte[] decoded = Base64.getMimeDecoder().decode(pemBody);
System.out.println(new String(decoded, StandardCharsets.UTF_8));
// -----BEGIN CERTIFICATE-----
// MIIBozCCAUigAwIBAgIJAIpaT2T...
}
}getMimeDecoder() มีความยืดหยุ่น: จะข้ามอักขระที่ไม่ถูกต้องแทนการ throw exception เหมาะสำหรับข้อมูล MIME ที่รู้จัก แต่อาจกลืน data corruption ในอย่างเงียบๆ สำหรับ input ทั่วไป ใช้ getDecoder() เมื่อต้องการ validation ที่เข้มงวดถอดรหัส Base64 จากไฟล์และ API Response
อ่านไฟล์ที่เข้ารหัส Base64 จากดิสก์
บางครั้ง binary file (รูปภาพ, certificate, encrypted blob) ถูกเก็บบนดิสก์ ในรูปแบบ Base64 text อ่านไฟล์, ถอดรหัส, แล้วเขียน binary output:
import java.util.Base64;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
public class DecodeFile {
public static void main(String[] args) {
Path inputPath = Path.of("tls-cert.pem.b64");
Path outputPath = Path.of("tls-cert.pem");
try {
String encoded = Files.readString(inputPath).strip();
byte[] decoded = Base64.getMimeDecoder().decode(encoded);
Files.write(outputPath, decoded);
System.out.printf("Decoded %d bytes → %s%n", decoded.length, outputPath);
} catch (IOException e) {
System.err.println("File error: " + e.getMessage());
} catch (IllegalArgumentException e) {
System.err.println("Invalid Base64: " + e.getMessage());
}
}
}ถอดรหัส Base64 field จาก HTTP API response
Cloud API (AWS KMS, GitHub Contents, Vault) มักคืน binary data เป็น Base64 string ภายใน JSON Parse JSON ก่อน แล้วถอดรหัส field ที่ต้องการ:
import java.util.Base64;
import java.nio.charset.StandardCharsets;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.net.URI;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
public class DecodeApiResponse {
public static void main(String[] args) {
HttpClient client = HttpClient.newHttpClient();
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create("https://api.example.com/secrets/db-password"))
.header("Authorization", "Bearer sk-prod-9f8e7d6c")
.build();
try {
HttpResponse<String> response = client.send(
request, HttpResponse.BodyHandlers.ofString());
if (response.statusCode() != 200) {
System.err.printf("Unexpected status: %d%n", response.statusCode());
return;
}
ObjectMapper mapper = new ObjectMapper();
JsonNode root = mapper.readTree(response.body());
// API returns: {"name":"db-password","value":"cG9zdGdyZXM6eGs5...","version":3}
String encodedValue = root.get("value").asText();
byte[] decoded = Base64.getDecoder().decode(encodedValue);
String secret = new String(decoded, StandardCharsets.UTF_8);
System.out.println("Secret: " + secret);
// Secret: postgres:xk9mP2qR@db-prod:5432/orders
} catch (Exception e) {
System.err.println("Failed to fetch secret: " + e.getMessage());
}
}
}IllegalArgumentException ต่างหากจาก network error การผสม I/O exception กับ decoding failure ทำให้ debug ยากขึ้น — คุณต้องรู้ทันทีว่า API คืนข้อมูลผิดหรือ network ล้มเหลวการถอดรหัส Base64 จาก Command Line
บางครั้งไม่จำเป็นต้องเขียน Java program ทุก Linux และ macOS มีคำสั่ง base64 และ JDK 9+ มี jshell สำหรับ Java one-liner แบบ interactive เมื่อต้องการตรวจสอบระหว่าง debug วิธีเหล่านี้เร็วกว่าการ compile class
# Decode a Base64 string (Linux / macOS)
echo "eyJob3N0IjoiMTAuMC4xLjUwIiwicG9ydCI6ODQ0M30=" | base64 --decode
# {"host":"10.0.1.50","port":8443}
# Decode and pretty-print with jq
echo "eyJob3N0IjoiMTAuMC4xLjUwIiwicG9ydCI6ODQ0M30=" | base64 --decode | jq .
# {
# "host": "10.0.1.50",
# "port": 8443
# }
# Quick decode with jshell (JDK 9+)
echo 'System.out.println(new String(java.util.Base64.getDecoder().decode("c2VydmVyLWNvbmZpZw==")))' | jshell -
# server-config
# macOS uses -D instead of --decode
echo "c2VydmVyLWNvbmZpZw==" | base64 -Dสำหรับการวาง encoded string ลงในเบราว์เซอร์โดยตรง Base64 decoder ของ ToolDeck รองรับทั้งรูปแบบมาตรฐานและ URL-safe โดยไม่ต้องตั้งค่าใดๆ
ทางเลือกประสิทธิภาพสูง: Apache Commons Codec
java.util.Base64 ที่มาพร้อม Java ได้รับการ optimize อย่างดีอยู่แล้ว — JDK 11+ ใช้ intrinsic บน x86 สำหรับ encoding และ decoding สำหรับแอปพลิเคชันส่วนใหญ่ ไม่มีเหตุผลต้องใช้ third-party library อย่างไรก็ตาม Apache Commons Codec ยังคงนิยม ใน legacy codebase และมี Base64InputStream สำหรับ streaming decoding พร้อมการจัดการ whitespace อัตโนมัติ
<!-- pom.xml -->
<dependency>
<groupId>commons-codec</groupId>
<artifactId>commons-codec</artifactId>
<version>1.17.0</version>
</dependency>import org.apache.commons.codec.binary.Base64;
public class CommonsCodecDecode {
public static void main(String[] args) {
// Commons Codec is more lenient — handles whitespace and line breaks
String encoded = "eyJob3N0IjoiMTAuMC4xLjUw\nIiwicG9ydCI6ODQ0M30=";
byte[] decoded = Base64.decodeBase64(encoded);
System.out.println(new String(decoded));
// {"host":"10.0.1.50","port":8443}
}
}ข้อได้เปรียบหลักของ Commons Codec เหนือ built-in API คือความยืดหยุ่นกับ whitespace โดยค่าเริ่มต้น และคลาส Base64InputStream ที่มีมาก่อน decoder.wrap() ของ Java ถ้าใช้ Java 8+ built-in API ครอบคลุมทุกสิ่งที่ Commons Codec ทำได้ ผมเลือกใช้ Commons Codec เฉพาะเมื่อ project มี dependency นั้นอยู่แล้ว
Streaming ไฟล์ Base64 ขนาดใหญ่ด้วย decoder.wrap()
การโหลดไฟล์ Base64 ขนาด 200 MB ด้วย Files.readString() แล้วเรียก decode() จะจัดสรร heap ประมาณ 350 MB: ทั้ง encoded string และ decoded byte array decoder.wrap(InputStream) ถอดรหัสแบบ on the fly ทำให้การใช้หน่วยความจำคงที่
import java.util.Base64;
import java.io.*;
import java.nio.file.*;
public class StreamDecode {
public static void main(String[] args) throws IOException {
Path src = Path.of("database-dump.sql.b64");
Path dst = Path.of("database-dump.sql");
try (InputStream in = Base64.getMimeDecoder().wrap(
new BufferedInputStream(Files.newInputStream(src)));
OutputStream out = new BufferedOutputStream(Files.newOutputStream(dst))) {
byte[] buffer = new byte[8192];
int bytesRead;
long total = 0;
while ((bytesRead = in.read(buffer)) != -1) {
out.write(buffer, 0, bytesRead);
total += bytesRead;
}
System.out.printf("Decoded %d bytes → %s%n", total, dst);
}
}
}บน Java 9+ สามารถแทน read loop ด้วย in.transferTo(out) — ทำสิ่งเดียวกัน ด้วยโค้ดน้อยกว่า ใช้ getMimeDecoder().wrap() แทน getDecoder().wrap() ถ้าไฟล์อาจมี line break (PEM file, email export)
import java.util.Base64;
import java.io.*;
import java.nio.file.*;
public class StreamDecodeSimple {
public static void main(String[] args) throws IOException {
try (InputStream in = Base64.getMimeDecoder().wrap(
new BufferedInputStream(Files.newInputStream(Path.of("backup.tar.b64"))));
OutputStream out = Files.newOutputStream(Path.of("backup.tar"))) {
in.transferTo(out); // Java 9+
}
}
}getDecoder().wrap() ไม่ รองรับ line break ใน stream ถ้าข้อมูล Base64 มี newline (ตัดบรรทัดทุก 76 อักขระ) ให้ใช้ getMimeDecoder().wrap() แทน — มิฉะนั้น stream จะผลิต output เสียหาย โดยไม่มีการแจ้งเตือน หรือ throw exception ในตำแหน่ง read ที่ไม่แน่นอนถอดรหัส JWT Payload ใน Java โดยไม่ใช้ JWT Library
JWT ประกอบด้วยสามส่วนที่เข้ารหัส Base64url คั่นด้วยจุด ส่วนกลางคือ payload — ส่วนที่สำคัญระหว่าง debug คุณถอดรหัสได้โดยไม่ต้องดึง jjwt หรือ Nimbus มาใช้ แค่ split ด้วย ., ถอดรหัสส่วนที่สองด้วย getUrlDecoder(), แล้ว parse JSON ที่ได้:
import java.util.Base64;
import java.nio.charset.StandardCharsets;
public class JWTInspect {
public static String decodeJwtPayload(String token) {
String[] parts = token.split("\\.");
if (parts.length != 3) {
throw new IllegalArgumentException(
"Invalid JWT: expected 3 segments, got " + parts.length);
}
// JWT uses URL-safe Base64 without padding
byte[] payload = Base64.getUrlDecoder().decode(parts[1]);
return new String(payload, StandardCharsets.UTF_8);
}
public static void main(String[] args) {
String token = "eyJhbGciOiJSUzI1NiJ9"
+ ".eyJzdWIiOiJ1c3ItNjcyIiwiaXNzIjoiYXV0aC5leGFtcGxlLmNvbSIs"
+ "ImV4cCI6MTc0MTk1NjgwMCwicm9sZXMiOlsiYWRtaW4iLCJiaWxsaW5nIl19"
+ ".SIGNATURE_PLACEHOLDER";
String payload = decodeJwtPayload(token);
System.out.println(payload);
// {"sub":"usr-672","iss":"auth.example.com",
// "exp":1741956800,"roles":["admin","billing"]}
}
}ข้อผิดพลาดที่พบบ่อย
ผมเจอทุกข้อนี้ในการ code review และสองข้อแรกเป็นสาเหตุส่วนใหญ่ ของ bug ที่เกี่ยวกับ Base64 ใน Java service บน production
ปัญหา: JWT token และ OAuth access token ใช้ตัวอักษรแบบ URL-safe (- และ _) การส่งให้ getDecoder() จะ throw IllegalArgumentException เพราะ - ไม่อยู่ใน Base64 alphabet มาตรฐาน
วิธีแก้: ตรวจสอบแหล่งข้อมูล: token จากระบบ auth ต้องใช้ getUrlDecoder(); MIME attachment ต้องใช้ getMimeDecoder()
// JWT header — URL-safe, no padding String header = "eyJhbGciOiJSUzI1NiJ9"; byte[] decoded = Base64.getDecoder().decode(header); // IllegalArgumentException: Illegal base64 character 2d
String header = "eyJhbGciOiJSUzI1NiJ9";
byte[] decoded = Base64.getUrlDecoder().decode(header);
System.out.println(new String(decoded));
// {"alg":"RS256"}ปัญหา: new String(bytes) ใช้ default charset ของ JVM ซึ่งแตกต่างกันในแต่ละ environment Linux CI server (UTF-8) และ Windows production host (Cp1252) ให้ผลลัพธ์ต่างกันสำหรับ byte เดียวกัน
วิธีแก้: ระบุ StandardCharsets.UTF_8 เป็นอาร์กิวเมนต์ที่สองเสมอ
byte[] decoded = Base64.getDecoder().decode(encoded); String result = new String(decoded); // platform-dependent — may corrupt multi-byte characters
byte[] decoded = Base64.getDecoder().decode(encoded); String result = new String(decoded, StandardCharsets.UTF_8); // consistent across all platforms
ปัญหา: Base64 string ที่วางจาก terminal หรืออ่านจาก config file มักมี trailing newline basic decoder ปฏิเสธอักขระทุกตัวที่ไม่อยู่ใน Base64 alphabet
วิธีแก้: เรียก .strip() บน input ก่อนถอดรหัส หรือเปลี่ยนเป็น getMimeDecoder() ซึ่งละเว้น whitespace
// Read from environment variable — has a trailing newline
String encoded = System.getenv("DB_PASSWORD_B64"); // "cG9zdGdyZXM=
"
byte[] decoded = Base64.getDecoder().decode(encoded);
// IllegalArgumentException: Illegal base64 character aString encoded = System.getenv("DB_PASSWORD_B64");
byte[] decoded = Base64.getDecoder().decode(encoded.strip());
System.out.println(new String(decoded, StandardCharsets.UTF_8));
// postgresปัญหา: การเรียก new String(decoded) บน binary content (รูปภาพ, protobuf, encrypted blob) ทำให้ String ไม่ถูกต้อง การแปลงกลับเป็น byte ในภายหลังจะเสียหายโดยไม่มีการแจ้งเตือน เพราะ String constructor แทนที่ UTF-8 sequence ที่ไม่ถูกต้อง
วิธีแก้: เก็บ binary data เป็น byte[] ตลอด pipeline ทั้งหมด แปลงเป็น String เฉพาะเมื่อรู้ว่า content เป็น text
byte[] decoded = Base64.getDecoder().decode(pngBase64);
String imageStr = new String(decoded); // corrupts binary
Files.writeString(Path.of("image.png"), imageStr); // broken filebyte[] decoded = Base64.getDecoder().decode(pngBase64);
// Write bytes directly — no String conversion
Files.write(Path.of("image.png"), decoded);เปรียบเทียบเมธอด
built-in decoder ครอบคลุม use case ส่วนใหญ่ Apache Commons Codec และ Guava เป็นทางเลือกที่อาจพบใน legacy codebase
สำหรับ JWT token และ API payload สมัยใหม่: getUrlDecoder() สำหรับ email attachment และ PEM certificate: getMimeDecoder() สำหรับไฟล์ขนาดใหญ่ ที่หน่วยความจำสำคัญ: decoder.wrap(InputStream) นอกนั้น: getDecoder() Apache Commons Codec สมเหตุสมผลเฉพาะเมื่อมีอยู่ใน dependency tree แล้ว
สำหรับการตรวจสอบรวดเร็วระหว่าง development online Base64 decoder เร็วกว่าการเขียน one-off class
คำถามที่พบบ่อย
จะถอดรหัส Base64 string ใน Java ได้อย่างไร?
import java.util.Base64 แล้วเรียก Base64.getDecoder().decode(encodedString) เมธอดนี้คืนค่าเป็น byte[] — ใช้ new String(bytes, StandardCharsets.UTF_8) เพื่อแปลงเป็นข้อความที่อ่านได้ สำหรับ Base64 แบบ URL-safe (ที่ใช้ใน JWT) ให้เปลี่ยน getDecoder() เป็น getUrlDecoder()
import java.util.Base64;
import java.nio.charset.StandardCharsets;
byte[] decoded = Base64.getDecoder().decode("c2VydmVyLWNvbmZpZw==");
String result = new String(decoded, StandardCharsets.UTF_8);
System.out.println(result); // server-configgetDecoder() กับ getMimeDecoder() ใน Java ต่างกันอย่างไร?
getDecoder() มีความเข้มงวด — จะปฏิเสธอักขระทุกตัวที่ไม่อยู่ใน Base64 alphabet รวมถึง line break ส่วน getMimeDecoder() รองรับตัวคั่นบรรทัด (\r\n) และละเว้นอักขระที่ไม่ใช่ Base64 จึงเหมาะสำหรับการถอดรหัส email attachment และ PEM certificate ที่มีการตัดบรรทัดทุก 76 อักขระ
String wrapped = "c2VydmVyLWNv\r\nbmZpZw=="; // getDecoder() throws IllegalArgumentException // Base64.getDecoder().decode(wrapped); // FAILS // getMimeDecoder() handles it byte[] decoded = Base64.getMimeDecoder().decode(wrapped); System.out.println(new String(decoded)); // server-config
จะถอดรหัส Base64 แบบ URL-safe ใน Java ได้อย่างไร?
ใช้ Base64.getUrlDecoder().decode(encoded) URL decoder รองรับตัวอักษร - และ _ ตาม RFC 4648 §5 แทน + และ / JWT token ใช้ตัวอักษรชุดนี้เสมอ หาก padding (=) ถูกตัดออก (ซึ่งพบบ่อยใน JWT) URL decoder ของ Java ก็ยังรองรับได้ ทั้งแบบมี padding และไม่มี padding
import java.util.Base64;
// JWT header — URL-safe, no padding
String jwtHeader = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9";
byte[] decoded = Base64.getUrlDecoder().decode(jwtHeader);
System.out.println(new String(decoded));
// {"alg":"HS256","typ":"JWT"}จะถอดรหัส Base64 แบบ streaming สำหรับไฟล์ขนาดใหญ่ใน Java ได้อย่างไร?
ใช้ decoder.wrap(inputStream) เพื่อ wrap FileInputStream InputStream ที่ได้จะถอดรหัส Base64 แบบ on the fly ขณะอ่าน byte ทำให้การใช้หน่วยความจำคงที่โดยไม่ขึ้นกับขนาดไฟล์ สามารถต่อ pipe กับ BufferedInputStream หรือส่งตรงไปยัง Files.copy() เพื่อประสิทธิภาพสูงสุด
import java.util.Base64;
import java.io.*;
import java.nio.file.*;
try (InputStream in = Base64.getDecoder().wrap(
new BufferedInputStream(new FileInputStream("payload.b64")));
OutputStream out = new FileOutputStream("payload.bin")) {
in.transferTo(out);
}ทำไม Base64.getDecoder().decode() ถึง throw IllegalArgumentException?
basic decoder มีความเข้มงวด: จะปฏิเสธ line break, space และอักขระทุกตัวที่ไม่อยู่ใน A-Za-z0-9+/= สาเหตุที่พบบ่อยมี 3 อย่าง: input มี trailing newline (ให้ trim ก่อน), input ใช้ตัวอักษรแบบ URL-safe เช่น - และ _ (ให้เปลี่ยนเป็น getUrlDecoder()), หรือ input มีการตัดบรรทัดทุก 76 อักขระ (ให้เปลี่ยนเป็น getMimeDecoder()) ตรวจสอบ raw byte เสมอหาก error message ไม่ชัดเจน
String raw = "c2VydmVyLWNvbmZpZw==\n"; // trailing newline // Option 1: trim whitespace byte[] decoded = Base64.getDecoder().decode(raw.strip()); // Option 2: use MIME decoder which ignores whitespace byte[] decoded2 = Base64.getMimeDecoder().decode(raw);
จะถอดรหัส Base64 ใน Java โดยไม่ใช้ java.util.Base64 ได้ไหม?
ได้ แต่ไม่มีเหตุผลดีพอสำหรับ Java 8+ ก่อน Java 8 นักพัฒนาใช้ sun.misc.BASE64Decoder (ภายใน ถูกลบใน Java 9+), javax.xml.bind.DatatypeConverter.parseBase64Binary() (ถูกลบใน Java 11) หรือ Apache Commons Codec ทั้งสามแบบล้าสมัยหรือต้องการ dependency เพิ่มเติม ควรใช้ java.util.Base64 เพราะเร็วกว่า มาพร้อม JDK และรองรับทั้งสามรูปแบบ (basic, URL-safe, MIME)
เครื่องมือที่เกี่ยวข้อง
- Base64 Encoder — เข้ารหัสข้อความหรือ binary data เป็น Base64 ในเบราว์เซอร์ สะดวกสำหรับสร้าง test fixture เพื่อวางใน Java unit test
- JWT Decoder — แยกและถอดรหัสทั้งสาม segment ของ JWT พร้อมตรวจสอบ payload แต่ละ field — เร็วกว่าการเขียน Java class เมื่อต้องการอ่าน token
- URL Decoder — ถอดรหัส URL-encoded string แบบ percent-decode มีประโยชน์เมื่อ API response รวม Base64url data กับ query parameter ที่ percent-encode ไว้
- JSON Formatter — หลังถอดรหัส Base64 JWT payload หรือ API config ให้วาง JSON ที่นี่ เพื่อ pretty-print และตรวจสอบโครงสร้าง