Giải Mã Base64 trong Java — Hướng Dẫn getDecoder().decode()

·Backend Engineer·Đánh giá bởiAisha Osei·Đã xuất bản

Sử dụng Giải mã Base64 Trực tuyến miễn phí trực tiếp trên trình duyệt — không cần cài đặt.

Dùng thử Giải mã Base64 Trực tuyến trực tuyến →

Giải mã Base64 trong Java là thứ tôi phải dùng đến vài ngày một lần — lấy secret từ biến môi trường Kubernetes, đọc payload nhị phân từ REST API, kiểm tra JWT token trong phiên debug. Lớp java.util.Base64 tích hợp sẵn trong Java (từ JDK 8) cung cấp ba loại bộ giải mã: getDecoder() cho Base64 chuẩn, getUrlDecoder() cho đầu vào URL-safe, và getMimeDecoder() cho dữ liệu có ngắt dòng như file đính kèm email. Để kiểm tra nhanh mà không cần viết code, công cụ Base64 decoder của ToolDeck xử lý ngay lập tức trên trình duyệt. Bài viết này nhắm vào Java 8+ và bao gồm cả ba bộ giải mã, streaming với wrap(InputStream), trích xuất payload JWT, giải mã file và phản hồi API, Apache Commons Codec như một lựa chọn thay thế, và bốn lỗi thường gặp khiến output bị sai trong môi trường production.

  • Base64.getDecoder().decode(s) là cách tiếp cận chuẩn — tích hợp sẵn trong java.util.Base64 từ JDK 8, không cần dependency ngoài.
  • Dùng getUrlDecoder() cho JWT token và OAuth payload — chúng dùng bảng chữ cái - và _, không phải + và /.
  • getMimeDecoder() bỏ qua ký tự xuống dòng và khoảng trắng, phù hợp cho file đính kèm email và chứng chỉ PEM.
  • decoder.wrap(InputStream) giải mã theo thời gian thực cho file lớn mà không cần tải toàn bộ vào bộ nhớ.
  • Bộ giải mã cơ bản rất nghiêm ngặt — ký tự xuống dòng cuối, dấu cách, hoặc ký tự sai bảng chữ cái sẽ ném IllegalArgumentException ngay lập tức.

Base64 Decoding là gì?

Mã hóa Base64 chuyển đổi dữ liệu nhị phân thành chuỗi ASCII 64 ký tự để có thể truyền an toàn qua các kênh chỉ hỗ trợ văn bản — trường JSON, HTTP header, tài liệu XML, nội dung email. Giải mã thực hiện quá trình ngược lại: mỗi 4 ký tự Base64 ánh xạ trở lại 3 byte gốc. Ký tự padding = ở cuối cho biết số byte đã được thêm để lấp đầy nhóm cuối. Base64 không phải là mã hóa bảo mật — ai cũng có thể đảo ngược nó. Mục đích của nó là an toàn truyền tải, không phải bảo mật.

Các tình huống giải mã Java điển hình: trích xuất giá trị cấu hình được inject dưới dạng biến môi trường Base64, giải nén nội dung file nhị phân từ phản hồi API đám mây, đọc chứng chỉ mã hóa PEM, và kiểm tra payload JWT token trong quá trình debug.

Before · text
After · text
ZGItcHJvZC51cy1lYXN0LTEuYW1hem9uYXdzLmNvbTo1NDMy
db-prod.us-east-1.amazonaws.com:5432

Base64.getDecoder().decode() — Phương thức giải mã chuẩn

Lớp java.util.Base64 được thêm vào JDK 8 và thay thế sun.misc.BASE64Decoder cũ mà mọi người từng dùng. Không cần dependency ngoài — chỉ cần import java.util.Base64 và gọi Base64.getDecoder().decode(). Phương thức này nhận String hoặc byte[] và trả về byte[] của dữ liệu đã giải mã.

Ví dụ tối giản hoạt động được

Java 8+
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
    }
}

Luôn chỉ định StandardCharsets.UTF_8 khi tạo String. Constructor new String(bytes) không có tham số sử dụng encoding mặc định của platform, vốn khác nhau giữa các hệ thống. Trên server Windows với Cp1252 là mặc định, các ký tự UTF-8 nhiều byte sẽ bị hỏng một cách thầm lặng.

Kiểm tra round-trip

Java 8+
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
    }
}

Giải mã vào bộ đệm được cấp phát sẵn

Overload ba tham số decode(byte[] src, byte[] dst) ghi trực tiếp vào bộ đệm đích và trả về số byte đã ghi. Điều này tránh được việc cấp phát thêm bộ nhớ trên các đường dẫn thực thi thường xuyên:

Java 8+
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}
    }
}
Lưu ý:decode() ném IllegalArgumentException nếu đầu vào chứa ký tự ngoài bảng chữ cái Base64 (kể cả ký tự xuống dòng và dấu cách). Nếu đầu vào có thể có khoảng trắng, hãy chuyển sang getMimeDecoder() hoặc loại bỏ nó bằng encoded.strip() trước khi giải mã.

Giải mã Base64 với kiểu dữ liệu phi chuẩn và đối tượng tùy chỉnh

byte[] thô từ decode() thường cần được chuyển thành thứ gì đó cụ thể hơn: UUID, đối tượng Java được serialize, protobuf message, hoặc timestamp. Bộ giải mã luôn trả về byte — việc chuyển đổi thành kiểu domain là trách nhiệm của bạn.

Base64 thành UUID

Java 8+
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 thành đối tượng JSON được deserialize với Jackson

Java 8+
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
    }
}
Cảnh báo:Không bao giờ dùng ObjectInputStream để deserialize dữ liệu Base64 không đáng tin cậy. Các cuộc tấn công deserialization Java đã được ghi chép rõ ràng — nếu nội dung được mã hóa đến từ nguồn bên ngoài, hãy parse nó dưới dạng JSON hoặc protobuf thay vì dùng Java native serialization.

Tài liệu tham khảo các phương thức Base64.Decoder

Tất cả phương thức thuộc java.util.Base64 và lớp bên trong của nó là Base64.Decoder. Ba phương thức factory trên Base64 trả về các instance bộ giải mã khác nhau; các phương thức decode() wrap() nằm trên instance Decoder.

Phương thức
Trả về
Kiểu đầu vào
Mô tả
getDecoder()
Base64.Decoder
Bộ giải mã chuẩn (RFC 4648 §4, bảng chữ cái + và / với padding =)
getUrlDecoder()
Base64.Decoder
Bộ giải mã URL-safe (RFC 4648 §5, bảng chữ cái - và _ với padding =)
getMimeDecoder()
Base64.Decoder
Bộ giải mã MIME — bỏ qua ký tự xuống dòng và ký tự không thuộc Base64
decode(String src)
byte[]
String
Giải mã chuỗi đầu vào thành mảng byte mới
decode(byte[] src)
byte[]
byte[]
Giải mã mảng byte đầu vào thành mảng byte mới
decode(byte[] src, byte[] dst)
int
byte[] + byte[]
Giải mã vào bộ đệm dst được cấp phát sẵn, trả về số byte đã ghi
wrap(InputStream is)
InputStream
InputStream
Trả về một stream giải mã dữ liệu Base64 theo thời gian thực

getMimeDecoder() — Giải mã Base64 có ngắt dòng và MIME

Bộ giải mã cơ bản từ chối bất kỳ thứ gì ngoài bảng chữ cái Base64 — bao gồm cả ký tự xuống dòng \r\n mà nội dung mã hóa MIME luôn chứa. File đính kèm email, chứng chỉ PEM, và một số phản hồi API cũ hơn ngắt dòng output Base64 sau 76 ký tự. getMimeDecoder() bỏ qua ký tự phân cách dòng và bất kỳ ký tự nào không thuộc bảng chữ cái Base64 một cách thầm lặng, nên nó xử lý điều này ngay lập tức.

Java 8+
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...
    }
}
Lưu ý:getMimeDecoder() rất linh hoạt: nó bỏ qua các ký tự không hợp lệ thay vì ném exception. Điều này ổn với dữ liệu MIME đã biết, nhưng có thể bỏ qua lỗi hỏng dữ liệu trong đầu vào tùy ý. Dùng getDecoder() khi bạn muốn xác thực nghiêm ngặt.

Giải mã Base64 từ file và phản hồi API

Đọc file được mã hóa Base64 từ ổ đĩa

Các file nhị phân (ảnh, chứng chỉ, blob được mã hóa) đôi khi được lưu trên ổ đĩa dưới dạng văn bản Base64. Đọc file, giải mã, ghi output nhị phân:

Java 8+
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());
        }
    }
}

Giải mã trường Base64 từ phản hồi API HTTP

Các API đám mây (AWS KMS, GitHub Contents, Vault) thường trả về dữ liệu nhị phân dưới dạng chuỗi Base64 bên trong JSON. Parse JSON trước, sau đó giải mã trường mục tiêu:

Java 11+
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());
        }
    }
}
Lưu ý:Bọc lệnh gọi decode trong try-catch riêng cho IllegalArgumentException tách biệt với các lỗi mạng. Trộn lẫn exception I/O với lỗi giải mã khiến debug khó hơn — bạn muốn biết ngay lập tức liệu API trả về dữ liệu sai hay mạng bị lỗi.

Giải mã Base64 từ dòng lệnh

Không phải lúc nào bạn cũng cần chương trình Java. Mọi hệ thống Linux và macOS đều có lệnh base64, và JDK 9+ đi kèm jshell để viết one-liner Java tương tác. Để kiểm tra nhanh trong quá trình debug, cách này nhanh hơn nhiều so với việc compile một class.

bash
# 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

Để dán chuỗi đã mã hóa trực tiếp vào trình duyệt, công cụ Base64 decoder của ToolDeck xử lý cả biến thể chuẩn và URL-safe mà không cần cài đặt gì.

Lựa chọn thay thế hiệu suất cao: Apache Commons Codec

java.util.Base64 tích hợp sẵn của Java đã được tối ưu tốt — JDK 11+ sử dụng intrinsics trên x86 cho encoding và decoding. Với hầu hết ứng dụng, không có lý do gì để dùng thư viện bên thứ ba. Tuy nhiên, Apache Commons Codec vẫn phổ biến trong các codebase cũ và cung cấp Base64InputStream cho streaming decoding với tự động xử lý khoảng trắng.

XML (Maven)
<!-- pom.xml -->
<dependency>
    <groupId>commons-codec</groupId>
    <artifactId>commons-codec</artifactId>
    <version>1.17.0</version>
</dependency>
Java 8+
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}
    }
}

Ưu điểm chính của Commons Codec so với API tích hợp là tính linh hoạt với khoảng trắng theo mặc định và lớp Base64InputStream có trước decoder.wrap() của Java. Nếu bạn đang dùng Java 8+, API tích hợp đã bao gồm mọi thứ mà Commons Codec làm được. Tôi chỉ dùng Commons Codec khi project đã phụ thuộc vào nó sẵn rồi.

Streaming file Base64 lớn với decoder.wrap()

Tải file Base64 200 MB bằng Files.readString() rồi gọi decode() sẽ cấp phát khoảng 350 MB heap: chuỗi đã mã hóa cộng với mảng byte đã giải mã. decoder.wrap(InputStream) giải mã theo thời gian thực, giữ bộ nhớ sử dụng không đổi.

Java 8+
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);
        }
    }
}

Trên Java 9+ bạn có thể thay vòng lặp đọc bằng in.transferTo(out) — nó làm điều tương tự với ít code hơn. Dùng getMimeDecoder().wrap() thay vì getDecoder().wrap() nếu file có thể chứa ký tự xuống dòng (file PEM, export email).

Java 9+
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+
        }
    }
}
Cảnh báo:getDecoder().wrap() không chấp nhận ký tự xuống dòng trong stream. Nếu dữ liệu Base64 có ký tự xuống dòng (ngắt dòng sau 76 ký tự), hãy dùng getMimeDecoder().wrap() thay thế — nếu không stream sẽ tạo ra output bị hỏng một cách thầm lặng hoặc ném exception tại vị trí đọc không xác định.

Giải mã payload JWT Base64 trong Java không cần thư viện JWT

JWT là ba segment được mã hóa Base64url nối nhau bằng dấu chấm. Segment giữa là payload — phần bạn quan tâm khi debug. Bạn có thể giải mã nó mà không cần kéo vào jjwt hay Nimbus. Tách theo ., giải mã phần thứ hai bằng getUrlDecoder(), và parse JSON kết quả:

Java 8+
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"]}
    }
}

Các lỗi thường gặp

Tôi đã gặp từng lỗi trong số này khi review code, và hai lỗi đầu chiếm phần lớn các bug production liên quan đến Base64 trong các Java service.

Dùng getDecoder() với đầu vào URL-safe

Vấn đề: JWT token và OAuth access token dùng bảng chữ cái URL-safe (- và _). Truyền chúng vào getDecoder() sẽ ném IllegalArgumentException vì - không có trong bảng chữ cái Base64 chuẩn.

Giải pháp: Kiểm tra nguồn dữ liệu: token từ auth system cần getUrlDecoder(); file đính kèm MIME cần getMimeDecoder().

Before · Java
After · Java
// 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"}
Không chỉ định charset khi tạo String

Vấn đề: new String(bytes) dùng charset mặc định của JVM, vốn khác nhau giữa các môi trường. Server CI Linux (UTF-8) và host production Windows (Cp1252) tạo ra kết quả khác nhau cho cùng một byte.

Giải pháp: Luôn truyền StandardCharsets.UTF_8 là tham số thứ hai.

Before · Java
After · Java
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
Giải mã chuỗi có khoảng trắng cuối

Vấn đề: Chuỗi Base64 dán từ terminal hoặc đọc từ file config thường có ký tự xuống dòng cuối. Bộ giải mã cơ bản từ chối bất kỳ ký tự nào ngoài bảng chữ cái Base64.

Giải pháp: Gọi .strip() trên đầu vào trước khi giải mã, hoặc chuyển sang getMimeDecoder() để bỏ qua khoảng trắng.

Before · Java
After · Java
// 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 a
String encoded = System.getenv("DB_PASSWORD_B64");
byte[] decoded = Base64.getDecoder().decode(encoded.strip());
System.out.println(new String(decoded, StandardCharsets.UTF_8));
// postgres
Chuyển đổi dữ liệu nhị phân thành String

Vấn đề: Gọi new String(decoded) trên nội dung nhị phân (ảnh, protobuf, blob được mã hóa) tạo ra String không hợp lệ. Chuyển ngược lại thành byte sau đó sẽ làm hỏng dữ liệu một cách thầm lặng vì constructor String thay thế các chuỗi UTF-8 không hợp lệ.

Giải pháp: Giữ dữ liệu nhị phân dưới dạng byte[] xuyên suốt pipeline của bạn. Chỉ chuyển sang String khi bạn biết chắc nội dung là văn bản.

Before · Java
After · Java
byte[] decoded = Base64.getDecoder().decode(pngBase64);
String imageStr = new String(decoded); // corrupts binary
Files.writeString(Path.of("image.png"), imageStr); // broken file
byte[] decoded = Base64.getDecoder().decode(pngBase64);
// Write bytes directly — no String conversion
Files.write(Path.of("image.png"), decoded);

So sánh các phương thức

Các bộ giải mã tích hợp bao gồm hầu hết các trường hợp sử dụng. Apache Commons Codec và Guava là lựa chọn thay thế bạn có thể gặp trong các codebase cũ hơn.

Phương thức
Biến thể mã hóa
Bỏ qua khoảng trắng
Streaming
Kiểu tùy chỉnh
Cần cài đặt
Base64.getDecoder()
Chuẩn (+, /)
Không (JDK 8+)
Base64.getUrlDecoder()
URL-safe (-, _)
Không (JDK 8+)
Base64.getMimeDecoder()
MIME (xuống dòng OK)
Không (JDK 8+)
decoder.wrap(InputStream)
Bất kỳ biến thể nào
Tùy bộ giải mã
Không (JDK 8+)
Apache Commons Base64InputStream
Chuẩn / URL-safe
Có (commons-codec)
Apache Commons Base64.decodeBase64()
Chuẩn
Có (commons-codec)
Guava BaseEncoding.base64().decode()
Chuẩn
Có (guava)

Với JWT token và API payload hiện đại: getUrlDecoder(). Với file đính kèm email và chứng chỉ PEM: getMimeDecoder(). Với file lớn quan trọng về bộ nhớ: decoder.wrap(InputStream). Mọi trường hợp khác: getDecoder(). Apache Commons Codec chỉ hợp lý nếu nó đã có sẵn trong dependency tree của bạn.

Để kiểm tra nhanh trong quá trình phát triển, công cụ Base64 decoder trực tuyến nhanh hơn việc viết một class one-off.

Câu hỏi thường gặp

Làm thế nào để giải mã chuỗi Base64 trong Java?

Import java.util.Base64 và gọi Base64.getDecoder().decode(encodedString). Phương thức này trả về byte[] — dùng new String(bytes, StandardCharsets.UTF_8) để chuyển thành văn bản đọc được. Với Base64 URL-safe (dùng trong JWT), thay getDecoder() bằng getUrlDecoder().

Java 8+
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-config

Sự khác biệt giữa getDecoder() và getMimeDecoder() trong Java là gì?

getDecoder() nghiêm ngặt — nó từ chối bất kỳ ký tự nào ngoài bảng chữ cái Base64, kể cả ký tự xuống dòng. getMimeDecoder() chấp nhận ký tự phân cách dòng (\r\n) và bỏ qua mọi ký tự không thuộc Base64, phù hợp để giải mã file đính kèm email và chứng chỉ PEM nơi dữ liệu được ngắt dòng sau 76 ký tự.

Java 8+
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

Làm thế nào để giải mã chuỗi Base64 URL-safe trong Java?

Dùng Base64.getUrlDecoder().decode(encoded). Bộ giải mã URL-safe sử dụng bảng chữ cái - và _ theo RFC 4648 §5 thay vì + và /. Token JWT luôn dùng bảng chữ cái này. Nếu ký tự padding (=) bị bỏ qua (thường gặp trong JWT), bộ giải mã URL của Java vẫn xử lý được — nó chấp nhận cả đầu vào có và không có padding.

Java 8+
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"}

Làm thế nào để giải mã stream file Base64 lớn trong Java?

Dùng decoder.wrap(inputStream) để bọc một FileInputStream. InputStream được trả về sẽ giải mã Base64 theo thời gian thực khi bạn đọc byte, giúp bộ nhớ sử dụng không đổi bất kể kích thước file. Kết hợp với BufferedInputStream hoặc đưa thẳng vào Files.copy() để đạt thông lượng tốt nhất.

Java 8+
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);
}

Tại sao Base64.getDecoder().decode() ném IllegalArgumentException?

Bộ giải mã cơ bản rất nghiêm ngặt: nó từ chối ký tự xuống dòng, dấu cách và bất kỳ ký tự nào ngoài A-Za-z0-9+/=. Ba nguyên nhân phổ biến: đầu vào có ký tự xuống dòng cuối (dùng trim), đầu vào dùng ký tự URL-safe như - và _ (chuyển sang getUrlDecoder()), hoặc đầu vào được ngắt dòng sau 76 ký tự (chuyển sang getMimeDecoder()). Hãy kiểm tra byte thô nếu thông báo lỗi không rõ ràng.

Java 8+
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);

Tôi có thể giải mã Base64 trong Java mà không dùng java.util.Base64 không?

Có, nhưng không có lý do gì để làm vậy trên Java 8+. Trước Java 8, các lập trình viên dùng sun.misc.BASE64Decoder (nội bộ, đã bị xóa trong Java 9+), javax.xml.bind.DatatypeConverter.parseBase64Binary() (đã bị xóa trong Java 11), hoặc Apache Commons Codec. Cả ba đều đã lỗi thời hoặc cần thêm dependency. Hãy dùng java.util.Base64 — nó nhanh hơn, được tích hợp trong JDK, và hỗ trợ cả ba biến thể (basic, URL-safe, MIME).

Công cụ liên quan

  • Base64 Encoder — mã hóa văn bản hoặc dữ liệu nhị phân thành Base64 trên trình duyệt, tiện lợi để tạo test fixture dán vào unit test Java của bạn.
  • JWT Decoder — tách và giải mã cả ba segment JWT cùng một lúc, với kiểm tra payload từng trường — nhanh hơn việc viết class Java khi bạn chỉ cần đọc một token.
  • URL Decoder — giải mã percent-encode chuỗi URL-encoded, hữu ích khi phản hồi API kết hợp dữ liệu Base64url với tham số query được percent-encode.
  • JSON Formatter — sau khi giải mã payload JWT Base64 hoặc config API, dán JSON vào đây để format và xác thực cấu trúc.
Cũng có sẵn trong:JavaScriptPythonGoC#
PN
Pavel NovakBackend Engineer

Pavel is a backend engineer with deep roots in the JVM ecosystem, working primarily with Java and Kotlin. He has extensive experience building data-intensive services and integrating third-party APIs at scale. He writes about modern Java features, the Jackson ecosystem, serialisation patterns, and practical approaches to keeping large codebases maintainable.

AO
Aisha OseiNgười đánh giá kỹ thuật

Aisha is a Java engineer specialising in application security, Spring Security, and API design. She has worked on identity and access management systems, OAuth 2.0 integrations, and microservice security at scale. She writes about secure Java coding practices, token validation, cryptographic utilities, and the Spring ecosystem from a security-first perspective.