Java Base64 编码 — 完全指南

·Java Security & API Engineer·审阅者Pavel Novak·发布日期

直接在浏览器中使用免费的 Base64编码器,无需安装。

在线试用 Base64编码器 →

每次配置 HTTP Basic Auth 请求头、将证书嵌入 Kubernetes Secret, 或通过 JSON API 传输二进制数据时,第一步都是一样的: Base64 编码原始字节,使其成为 ASCII 安全字符串。 Java 通过 java.util.Base64 让这一操作变得简单明了——这是自 Java 8 起内置的标准 API, 替代了已废弃的 sun.misc.BASE64Encoder。 如果只需快速编码而不想写代码, ToolDeck 的 Base64 编码器 可在浏览器中即时处理。 本文涵盖 Base64.getEncoder() getUrlEncoder() getMimeEncoder()、 文件编码、使用 wrap(OutputStream) 进行流式处理,以及即便是经验丰富的 Java 开发者也容易踩的坑。 所有示例均可在 Java 8 到 Java 21+ 上编译运行。

  • Base64.getEncoder().encodeToString(bytes) 是标准的一行式写法——自 Java 8 起内置于 JDK,在 Java 17 和 21 中保持不变。
  • 编码前务必向 String.getBytes() 传入 StandardCharsets.UTF_8——省略它会使用平台默认编码,不同 JVM 结果各异。
  • getUrlEncoder() 生成 URL 安全输出(- 替代 +,_ 替代 /),withoutPadding() 去除末尾的 = 字符。
  • getMimeEncoder() 每 76 个字符插入换行——电子邮件(MIME)和 PEM 证书格式要求此格式。
  • 处理大文件时,使用 Base64.getEncoder().wrap(OutputStream) 进行流式处理,避免将整个文件载入内存。

什么是 Base64 编码?

Base64 将任意二进制数据转换为由 64 个可打印 ASCII 字符组成的字符串: A-Z、a-z、0-9、 + /。 每 3 个输入字节恰好生成 4 个 Base64 字符。若输入长度不是 3 的倍数, 则追加一或两个 = 填充字符。编码后的输出比原始数据大约大 33%。

Base64 不是加密。任何人拿到编码字符串都可以解码。它的用途是传输安全性: HTTP 请求头、JSON 载荷、XML 文档和电子邮件正文都是基于文本的协议, 无法直接传输原始二进制字节而不产生乱码。Java 中的常见使用场景包括 HTTP Basic 认证、嵌入 PEM 证书、将二进制数据存储在数据库文本列, 以及构建 JWT 令牌段。

Before · text
After · text
deploy-svc:sk_live_4eC39HqLyjWDarjtT1zdp7dc
ZGVwbG95LXN2Yzpza19saXZlXzRlQzM5SHFMeWpXRGFyanRUMXpkcDdkYw==

Base64.getEncoder().encodeToString() — 标准 API

java.util.Base64 在 Java 8 中作为 sun.misc.BASE64Encoder 的官方替代品引入。该类提供三个静态工厂方法,每个方法返回一个 Base64.Encoder 嵌套类实例,分别对应 RFC 4648 定义的三种 Base64 变体。无需第三方库,无需 Maven 依赖, 直接导入调用即可。

最简示例——对字符串编码

Java 8+
import java.util.Base64;
import java.nio.charset.StandardCharsets;

public class EncodeDemo {
    public static void main(String[] args) {
        String credentials = "monitoring-svc:9f2a7c4e-b1d8-4a3f";
        byte[] credentialBytes = credentials.getBytes(StandardCharsets.UTF_8);

        String encoded = Base64.getEncoder().encodeToString(credentialBytes);
        System.out.println(encoded);
        // bW9uaXRvcmluZy1zdmM6OWYyYTdjNGUtYjFkOC00YTNm
    }
}

很多 Java 开发者第一次使用时会忽略的关键步骤: String 必须先转换为 byte[] 再进行编码。Base64 操作的是字节,而非字符。 encodeToString() 接受 byte[] 并直接返回 Base64 String。 如果需要将编码结果作为字节返回,使用 encode(byte[])—— 它返回 ASCII 编码 Base64 字符的 byte[], 适合直接写入 OutputStream 或构建二进制协议帧时使用。

HTTP Basic Auth——最常见的使用场景

HTTP Basic 认证可能是 Java 开发者使用 Base64 编码最频繁的场景。 规范(RFC 7617)要求将凭据字符串 username:password 进行 Base64 编码后放入 Authorization 请求头。 我见过无数次错误写法——通常是忘记冒号分隔符,或将两个组成部分分别编码。

Java — HTTP Basic Auth 请求头
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.util.Base64;
import java.nio.charset.StandardCharsets;

public class BasicAuthExample {
    public static void main(String[] args) throws Exception {
        String username = "metrics-exporter";
        String apiKey = "sk_live_4eC39HqLyjWDarjtT1zdp7dc";

        // username:password → Base64
        String credentials = username + ":" + apiKey;
        String authHeader = "Basic " + Base64.getEncoder()
            .encodeToString(credentials.getBytes(StandardCharsets.UTF_8));

        HttpRequest request = HttpRequest.newBuilder()
            .uri(URI.create("https://api.example.com/v2/metrics"))
            .header("Authorization", authHeader)
            .build();

        HttpResponse<String> response = HttpClient.newHttpClient()
            .send(request, HttpResponse.BodyHandlers.ofString());

        System.out.println(response.statusCode());  // 200
    }
}

往返验证——编码与解码

Java 8+ — 编码与解码往返
import java.util.Base64;
import java.nio.charset.StandardCharsets;

public class RoundTrip {
    public static void main(String[] args) {
        String original = "X-Correlation-ID: req_8a4f2c91-e7b3-4d56-9012-3f7a8b9c0d1e";

        // 编码
        String encoded = Base64.getEncoder()
            .encodeToString(original.getBytes(StandardCharsets.UTF_8));
        System.out.println(encoded);
        // WC1Db3JyZWxhdGlvbi1JRDogcmVxXzhhNGYyYzkxLWU3YjMtNGQ1Ni05MDEyLTNmN2E4YjljMGQxZQ==

        // 解码
        byte[] decodedBytes = Base64.getDecoder().decode(encoded);
        String decoded = new String(decodedBytes, StandardCharsets.UTF_8);

        System.out.println(original.equals(decoded));  // true
    }
}
注意:java.util.Base64 API 从 Java 8 到 Java 17、Java 21 完全一致。升级 JDK 时无需任何迁移。同一份代码可在 Java 8 起的任意版本上编译运行。

编码非字符串数据——byte[]、UUID 和时间戳

Java 中的 Base64 编码始终从 byte[] 开始。字符串通过 getBytes(StandardCharsets.UTF_8) 转换,但其他类型需要先经过转换步骤。UUID、时间戳和数值标识符必须先序列化为 字符串或字节表示,才能进行 Base64 编码。

UUID——编码字符串表示

Java — 对 UUID 进行 Base64 编码
import java.util.Base64;
import java.util.UUID;
import java.nio.charset.StandardCharsets;

UUID sessionId = UUID.fromString("6ba7b810-9dad-11d1-80b4-00c04fd430c8");
String encoded = Base64.getEncoder()
    .encodeToString(sessionId.toString().getBytes(StandardCharsets.UTF_8));
System.out.println(encoded);
// NmJhN2I4MTAtOWRhZC0xMWQxLTgwYjQtMDBjMDRmZDQzMGM4

紧凑 UUID——编码原始 16 字节

若需要更短的编码结果,可将 UUID 的 128 位直接提取为 16 个原始字节, 而非转换为 36 字符的字符串形式。Base64 输出将从 48 个字符缩短为 24 个字符。

Java — 紧凑 UUID 编码
import java.nio.ByteBuffer;
import java.util.Base64;
import java.util.UUID;

UUID eventId = UUID.fromString("550e8400-e29b-41d4-a716-446655440000");

ByteBuffer buffer = ByteBuffer.wrap(new byte[16]);
buffer.putLong(eventId.getMostSignificantBits());
buffer.putLong(eventId.getLeastSignificantBits());

String compact = Base64.getUrlEncoder()
    .withoutPadding()
    .encodeToString(buffer.array());
System.out.println(compact);
// VQ6EAOKbQdSnFkRmVUQAAA
// 22 个字符,而字符串方式需要 48 个字符

时间戳与混合载荷

Java — 对含时间戳的 JSON 类载荷编码
import java.time.Instant;
import java.util.Base64;
import java.nio.charset.StandardCharsets;

// 模拟 JWT 风格的载荷
String payload = String.format(
    "{"sub":"usr_7b3c","iss":"auth.internal","iat":%d,"exp":%d}",
    Instant.now().getEpochSecond(),
    Instant.now().plusSeconds(3600).getEpochSecond()
);

String encoded = Base64.getUrlEncoder()
    .withoutPadding()
    .encodeToString(payload.getBytes(StandardCharsets.UTF_8));
System.out.println(encoded);
// eyJzdWIiOiJ1c3JfN2IzYyIsImlzcyI6ImF1dGguaW50ZXJuYWwiLCJpYXQiOj... (URL 安全,无填充)
警告:不要对 byte[] 调用 toString() 并期望得到其内容—— 那只会返回数组的标识哈希,如 [B@6d06d69c。 请使用 new String(bytes, StandardCharsets.UTF_8), 或直接将字节数组传入 encodeToString()

Base64.Encoder 方法参考

java.util.Base64 类提供三个工厂方法,每个方法返回一个针对特定变体配置的 Base64.Encoder。 编码器实例是线程安全且无状态的——创建一次,反复复用。

方法
类型
说明
getEncoder()
Base64.Encoder
返回符合 RFC 4648 标准的基础编码器,使用标准字母表(A-Z、a-z、0-9、+、/)
getUrlEncoder()
Base64.Encoder
返回 URL 安全字母表的编码器(- 替代 +,_ 替代 /)
getMimeEncoder()
Base64.Encoder
返回 MIME 编码器,每 76 个字符插入 \r\n 换行
getMimeEncoder(lineLength, lineSeparator)
Base64.Encoder
自定义行长度和分隔符的 MIME 编码器
encoder.withoutPadding()
Base64.Encoder
返回省略末尾 = 填充字符的编码器
encoder.encode(byte[])
byte[]
编码字节数组,返回编码后的字节数组
encoder.encodeToString(byte[])
String
编码字节数组,直接返回编码后的 String
encoder.wrap(OutputStream)
OutputStream
包装 OutputStream 以支持流式 Base64 编码

Base64.getUrlEncoder() — URL 安全编码

URL 安全编码器使用替代字母表,将 + 替换为 -, 将 / 替换为 _, 符合 RFC 4648 第 5 节的定义。当 Base64 字符串出现在 URL 查询参数、文件名或 Cookie 值中时, 这一点至关重要——标准 Base64 字符与 URL 分隔符和文件系统保留字符存在冲突。

Java — URL 安全 Base64 编码
import java.util.Base64;
import java.nio.charset.StandardCharsets;

String redirectUri = "https://app.internal/callback?state=auth_pending&nonce=9f2a7c";
byte[] data = redirectUri.getBytes(StandardCharsets.UTF_8);

// 标准编码器——包含 + 和 / 会破坏 URL
String standard = Base64.getEncoder().encodeToString(data);
System.out.println(standard);
// aHR0cHM6Ly9hcHAuaW50ZXJuYWwvY2FsbGJhY2s/c3RhdGU9YXV0aF9wZW5kaW5nJm5vbmNlPTlmMmE3Yw==

// URL 安全编码器——适合查询参数和文件名
String urlSafe = Base64.getUrlEncoder().encodeToString(data);
System.out.println(urlSafe);
// aHR0cHM6Ly9hcHAuaW50ZXJuYWwvY2FsbGJhY2s_c3RhdGU9YXV0aF9wZW5kaW5nJm5vbmNlPTlmMmE3Yw==

// URL 安全且无填充——用于 JWT 和紧凑令牌
String noPadding = Base64.getUrlEncoder().withoutPadding().encodeToString(data);
System.out.println(noPadding);
// aHR0cHM6Ly9hcHAuaW50ZXJuYWwvY2FsbGJhY2s_c3RhdGU9YXV0aF9wZW5kaW5nJm5vbmNlPTlmMmE3Yw

withoutPadding() 变体会去除末尾的 = 字符。JWT 规范要求 header 和 payload 段使用无填充的 URL 安全 Base64, 因此手动构建或处理 JWT 令牌时, getUrlEncoder().withoutPadding() 正是你需要的调用。

注意:withoutPadding() 方法返回一个新的编码器实例—— 不会修改原始实例。两者都可以赋值给 static final 字段,在多线程环境中安全复用。

从文件和 API 响应编码

Java 中 Base64 编码最常见的两个实际场景:从磁盘读取二进制文件(证书、图片、配置包) 以及对 HTTP 响应中接收到的数据进行编码。

将文件编码为 Base64

Java — 文件编码
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.Base64;

public class FileEncoder {
    public static void main(String[] args) {
        try {
            byte[] fileBytes = Files.readAllBytes(Path.of("certs/server.pem"));
            String encoded = Base64.getEncoder().encodeToString(fileBytes);

            System.out.printf("原始大小:%d 字节%n", fileBytes.length);
            System.out.printf("编码后:%d 个字符%n", encoded.length());

            // 将编码内容写入文本文件
            Files.writeString(
                Path.of("certs/server.pem.b64"),
                encoded
            );
        } catch (java.io.IOException e) {
            System.err.println("读取文件失败:" + e.getMessage());
        }
    }
}

对 API 响应体编码

Java 11+ — 对 HTTP 响应编码
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.util.Base64;

public class ApiEncoder {
    public static void main(String[] args) {
        try {
            HttpClient client = HttpClient.newHttpClient();
            HttpRequest request = HttpRequest.newBuilder()
                .uri(URI.create("https://api.example.com/v2/reports/weekly.pdf"))
                .header("Authorization", "Bearer tok_8f2a9c3d")
                .build();

            HttpResponse<byte[]> response = client.send(
                request, HttpResponse.BodyHandlers.ofByteArray()
            );

            if (response.statusCode() == 200) {
                String encoded = Base64.getEncoder()
                    .encodeToString(response.body());
                System.out.printf("已编码 %d 字节 → %d 个字符%n",
                    response.body().length, encoded.length());
            } else {
                System.err.printf("HTTP %d: %s%n",
                    response.statusCode(),
                    new String(response.body()));
            }
        } catch (Exception e) {
            System.err.println("请求失败:" + e.getMessage());
        }
    }
}

在命令行部分之前说一句:如果只需粘贴文件或 API 响应内容并获取 Base64 输出, 无需编写代码, 在线 Base64 编码器 支持文本和二进制输入。

命令行 Base64 编码

有时你只需要在终端对字符串或文件进行编码——不需要 Java 项目、IDE 或构建步骤。 大多数 Unix 系统自带 base64 命令,如果安装了 JDK,也可以使用 jshell 以 Java 原生方式处理。

Bash — 命令行 Base64 编码
# macOS / Linux——对字符串编码
echo -n "deploy-bot:sk_prod_9f2a7c4e" | base64
# ZGVwbG95LWJvdDpza19wcm9kXzlmMmE3YzRl

# 对文件编码
base64 < certs/server.pem > certs/server.pem.b64

# 使用 jshell(JDK 9+)
echo 'System.out.println(java.util.Base64.getEncoder().encodeToString("deploy-bot:sk_prod_9f2a7c4e".getBytes()))' | jshell -

# 使用 java 直接执行单行代码
java -e 'System.out.println(java.util.Base64.getEncoder().encodeToString(args[0].getBytes()))' "my-secret"
# 注意:java -e 需要 JDK 23+(JEP 477)

jshell 方式在验证 Java 代码与 Unix 工具产生相同输出时特别有用, 或者在调试服务发送内容与接收方预期之间的差异时也很方便。 我为此设置了一个 shell 别名。

注意:在 macOS 上,base64 命令使用 -D 进行解码。在 Linux(GNU coreutils)上使用 -d。两者的编码行为完全相同。 Linux 上的 -w 0 标志可禁用输出中的换行, 在管道传输给其他命令时通常需要这个选项。

Apache Commons Codec — 高性能替代方案

对于大多数应用,java.util.Base64 的性能已足够。但如果在紧密循环中处理数百万次编码操作——比如日志摄取管道或 高吞吐量消息中间件——Apache Commons Codec 值得进行基准测试。 它早在 Java 8 之前就已存在,提供经过充分验证的替代实现,API 接口略有不同。

Java — Apache Commons Codec
// Maven: org.apache.commons:commons-codec:1.17.0
import org.apache.commons.codec.binary.Base64;
import java.nio.charset.StandardCharsets;

byte[] telemetryPayload = ("{"service":"metrics-collector","
    + ""host":"prod-east-07","
    + ""cpu_pct":72.4,"
    + ""mem_mb":3891,"
    + ""timestamp":1710523200}")
    .getBytes(StandardCharsets.UTF_8);

// 标准编码
String encoded = Base64.encodeBase64String(telemetryPayload);

// URL 安全编码
String urlSafe = Base64.encodeBase64URLSafeString(telemetryPayload);

// 检查字符串是否为有效 Base64
boolean valid = Base64.isBase64(encoded);
System.out.println(valid);  // true

Apache Commons Codec 还提供了用于流式场景的 Base64OutputStream Base64InputStream, 以及 JDK 编码器所没有的校验方法。 如果 Commons Codec 已在你的依赖树中(它随许多 Apache 项目一同引入), 直接使用完全合理。

Guava BaseEncoding

Google 的 Guava 库包含 BaseEncoding, 提供流畅的 Base64 API,支持可配置的行分隔符、填充控制, 以及标准和 URL 安全两种字母表。API 可读性很好,但仅为 Base64 编码引入 Guava(约 3 MB) 是过度依赖。如果你的项目已因集合或缓存工具引入了 Guava,那么编码 API 是个不错的附加收益。

Java — Guava BaseEncoding
// Maven: com.google.guava:guava:33.1.0-jre
import com.google.common.io.BaseEncoding;
import java.nio.charset.StandardCharsets;

byte[] webhookPayload = ("{"event":"deployment.completed","
    + ""repo":"payments-api","
    + ""sha":"a7f2c91e4b3d","
    + ""environment":"production"}")
    .getBytes(StandardCharsets.UTF_8);

// 标准 Base64
String standard = BaseEncoding.base64().encode(webhookPayload);

// URL 安全
String urlSafe = BaseEncoding.base64Url().encode(webhookPayload);

// 无填充
String noPad = BaseEncoding.base64Url().omitPadding().encode(webhookPayload);

// 带行分隔符(PEM 风格)
String wrapped = BaseEncoding.base64()
    .withSeparator("\n", 64)
    .encode(webhookPayload);

Base64.getMimeEncoder() — MIME 和 PEM 换行输出

MIME 编码器每 76 个字符插入 \r\n 换行,符合 MIME 规范(RFC 2045)。PEM 证书、S/MIME 邮件附件和部分旧版 API 都期望这种格式。标准编码器和 URL 安全编码器生成的是单行连续字符串—— 如果将其输出传给期望换行 Base64 的系统,可能会静默失败或拒绝数据。

Java — MIME Base64 编码
import java.util.Base64;
import java.nio.charset.StandardCharsets;

// 模拟 PEM 证书体
byte[] certData = new byte[256];  // 实际使用时从 .der 文件读取
new java.security.SecureRandom().nextBytes(certData);

// 默认 MIME 编码器——每行 76 个字符,\r\n 分隔
String mimeEncoded = Base64.getMimeEncoder().encodeToString(certData);
System.out.println(mimeEncoded);
// QYx2K3p8Xg7JmN1R+wFkLd...  (76 个字符)
// Ht5Bv9CzAq0PnSjYl8WxUe...  (76 个字符)
// ...

// 自定义 MIME 编码器——每行 64 个字符(PEM 标准),\n 分隔
Base64.Encoder pemEncoder = Base64.getMimeEncoder(64, new byte[]{'\n'});
String pemBody = pemEncoder.encodeToString(certData);
System.out.println("-----BEGIN CERTIFICATE-----");
System.out.println(pemBody);
System.out.println("-----END CERTIFICATE-----");
警告:不要对 JWT 令牌、HTTP 请求头或 URL 参数使用 getMimeEncoder()。 换行符会在这些上下文中破坏数据。 请改用 getEncoder() getUrlEncoder()

使用 Base64.getEncoder().wrap() 流式处理大文件

Files.readAllBytes() 将整个文件读入 byte[] 对小文件可行,但超过 50-100 MB 的文件可能触发 OutOfMemoryError。 JDK 提供了 Base64.getEncoder().wrap(OutputStream), 返回一个 OutputStream, 在写入数据时实时进行编码。编码后的字节直接流向底层流,无需缓冲整个输入。

Java — 流式 Base64 编码
import java.io.*;
import java.nio.file.*;
import java.util.Base64;

public class StreamingEncoder {
    public static void main(String[] args) throws IOException {
        Path inputPath = Path.of("backups/database-export.sql.gz");
        Path outputPath = Path.of("backups/database-export.sql.gz.b64");

        try (
            InputStream in = Files.newInputStream(inputPath);
            OutputStream fileOut = Files.newOutputStream(outputPath);
            OutputStream base64Out = Base64.getEncoder().wrap(fileOut)
        ) {
            byte[] buffer = new byte[8192];
            int bytesRead;
            long totalBytes = 0;

            while ((bytesRead = in.read(buffer)) != -1) {
                base64Out.write(buffer, 0, bytesRead);
                totalBytes += bytesRead;
            }

            System.out.printf("已流式处理 %d 字节通过 Base64 编码器%n", totalBytes);
        }
        // 关闭 base64Out 会自动刷新最终的填充字节
    }
}

try-with-resources 块负责刷新和关闭流。一个容易忽略的细节: 最终的 Base64 填充只在包装的 OutputStream 关闭时才会写入。如果忘记关闭它(或只关闭了外层流), 编码输出的最后几个字符(包括填充)可能会丢失。

流式写入网络 Socket

wrap() 方法适用于任何 OutputStream—— 文件输出、Socket 输出、HTTP 响应体,甚至 ByteArrayOutputStream。 以下示例将 Base64 编码数据直接写入内存缓冲区,适合单元测试或构建将通过 HTTP 发送的载荷:

Java — 流式写入 ByteArrayOutputStream
import java.io.ByteArrayOutputStream;
import java.io.OutputStream;
import java.util.Base64;
import java.nio.charset.StandardCharsets;

ByteArrayOutputStream buffer = new ByteArrayOutputStream();

try (OutputStream encoder = Base64.getEncoder().wrap(buffer)) {
    // 分块写入数据——模拟从流中读取
    encoder.write("chunk-1:telemetry-data-".getBytes(StandardCharsets.UTF_8));
    encoder.write("chunk-2:more-payload-".getBytes(StandardCharsets.UTF_8));
    encoder.write("chunk-3:final-segment".getBytes(StandardCharsets.UTF_8));
}

String encoded = buffer.toString(StandardCharsets.UTF_8);
System.out.println(encoded);
// Y2h1bmstMTp0ZWxlbWV0cnktZGF0YS1jaHVuay0yOm1vcmUtcGF5bG9hZC1jaHVuay0zOmZpbmFsLXNlZ21lbnQ=

// 验证往返
byte[] decoded = Base64.getDecoder().decode(encoded);
System.out.println(new String(decoded, StandardCharsets.UTF_8));
// chunk-1:telemetry-data-chunk-2:more-payload-chunk-3:final-segment
注意:流式示例中的缓冲区大小(8192 字节)并非随意选取。它与 BufferedInputStream 的默认缓冲区大小一致,在内存占用和系统调用开销之间取得了良好平衡。 缓冲区过小会增加读写次数;缓冲区过大则浪费内存,吞吐量提升有限。

线程安全的编码器实例——存储与复用

工厂方法返回的 Base64.Encoder 是不可变且线程安全的。每次编码都调用 Base64.getEncoder() 会每次创建一个新对象。JVM 很可能会将其优化掉,但将编码器存储在 static final 字段中,意图更清晰,也避免了热路径中不必要的对象分配。

Java — 可复用编码器实例
import java.util.Base64;
import java.nio.charset.StandardCharsets;

public class TokenService {
    // 创建一次,到处复用——线程安全
    private static final Base64.Encoder STANDARD = Base64.getEncoder();
    private static final Base64.Encoder URL_SAFE = Base64.getUrlEncoder().withoutPadding();
    private static final Base64.Encoder MIME = Base64.getMimeEncoder();

    public static String encodeForHeader(String value) {
        return STANDARD.encodeToString(value.getBytes(StandardCharsets.UTF_8));
    }

    public static String encodeForUrl(byte[] data) {
        return URL_SAFE.encodeToString(data);
    }

    public static String encodeForEmail(byte[] attachment) {
        return MIME.encodeToString(attachment);
    }
}

这种模式在 Spring Boot 服务中尤其有用——工具类需要在多个 Controller 或 Service 方法中 处理编码。 withoutPadding() 调用返回新的编码器实例,因此可以将有填充和无填充两个变体分别存为独立字段。 每次调用 encodeToString() encode() 都是无状态的——无需同步,没有共享可变状态。

常见错误

调用 getBytes() 时未指定字符集

问题: 不带字符集参数调用 String.getBytes() 会使用平台默认编码:Windows 上是 windows-1252,大多数 Linux 系统是 UTF-8,macOS 上不固定。同一份代码在不同机器上会产生不同的 Base64 输出。

解决方案: 始终显式传入 StandardCharsets.UTF_8。

Before · Java
After · Java
String text = "Ключ доступа: prod-east";
byte[] bytes = text.getBytes();  // 平台默认——结果不可预测
String encoded = Base64.getEncoder().encodeToString(bytes);
String text = "Ключ доступа: prod-east";
byte[] bytes = text.getBytes(StandardCharsets.UTF_8);
String encoded = Base64.getEncoder().encodeToString(bytes);
对 URL 参数使用标准编码器

问题: Base64.getEncoder() 输出包含 + 和 / 字符。放入 URL 查询字符串时,+ 会被解析为空格,/ 会被解析为路径分隔符,在接收端静默破坏值。

解决方案: 任何将出现在 URL 中的值都使用 Base64.getUrlEncoder()。

Before · Java
After · Java
// URL 查询参数中的令牌——会出错
String token = Base64.getEncoder()
    .encodeToString(sessionData);
String url = "https://auth.internal/verify?token=" + token;
// URL 安全编码——不含 + 或 / 字符
String token = Base64.getUrlEncoder()
    .withoutPadding()
    .encodeToString(sessionData);
String url = "https://auth.internal/verify?token=" + token;
用错误的编码器变体解码

问题: 用 getUrlEncoder() 编码后用 getDecoder() 解码(或反之)会抛出 IllegalArgumentException,因为 - 和 _ 在标准 Base64 字母表中无效,而 + 和 / 在 URL 安全字母表中无效。

解决方案: 始终使用匹配的解码器:URL 安全编码对应 getUrlDecoder(),标准编码对应 getDecoder()。

Before · Java
After · Java
String encoded = Base64.getUrlEncoder()
    .encodeToString(data);
// 后续...
byte[] decoded = Base64.getDecoder()  // 错误的解码器
    .decode(encoded);
// 若 encoded 包含 - 或 _ 则抛出 IllegalArgumentException
String encoded = Base64.getUrlEncoder()
    .encodeToString(data);
// 后续...
byte[] decoded = Base64.getUrlDecoder()  // 匹配的解码器
    .decode(encoded);
未关闭 wrap() 的 OutputStream

问题: 流式编码器会缓冲最多 2 个输入字节,等待凑齐完整的 3 字节组。如果不关闭包装的 OutputStream,最后 1-4 个 Base64 字符(包括填充)将永远不会写出。

解决方案: 使用 try-with-resources,或在读取输出之前显式调用包装流的 close()。

Before · Java
After · Java
ByteArrayOutputStream baos = new ByteArrayOutputStream();
 OutputStream b64os = Base64.getEncoder().wrap(baos);
b64os.write(data);
// baos.toString() 输出不完整——缺少最后几个字节
ByteArrayOutputStream baos = new ByteArrayOutputStream();
try (OutputStream b64os = Base64.getEncoder().wrap(baos)) {
    b64os.write(data);
}  // close() 刷新最终填充
String encoded = baos.toString();  // 完整输出

Base64 编码方法对比

方法
URL 安全
流式处理
换行
自定义类型
需要安装
Base64.getEncoder()
✓ (wrap)
否(JDK 8+)
Base64.getUrlEncoder()
✓ (wrap)
否(JDK 8+)
Base64.getMimeEncoder()
✓ (wrap)
✓(76 字符)
否(JDK 8+)
Apache Commons Codec
Maven 依赖
Guava BaseEncoding
✓(可配置)
Maven 依赖
jcmd / CLI base64
✓(管道)
N/A
系统安装

对于大多数项目: java.util.Base64 是正确选择。零依赖、内置于 JDK、线程安全,且涵盖全部三种 RFC 4648 变体。 只有在 Apache Commons Codec 已经在你的 classpath 中,且需要 isBase64() 校验方法或流式 Base64OutputStream 时才考虑使用它。如果项目已依赖 Guava, BaseEncoding 是合理选项,但仅为 Base64 引入 3 MB 依赖实在难以说服自己。

三种场景,三个选择:需要 Basic Auth 或 JWT 编码的标准 Web 服务?用 JDK 就够了。 遗留项目已通过 Spring 或 Apache HTTP Client 引入了 Commons Codec?用它—— 没必要在 classpath 里同时放两个 Base64 库。项目用 Guava 做缓存和集合? 用 BaseEncoding—— 其流畅 API 很清晰。绝不要只为 Base64 编码引入依赖——JDK 自 2014 年起的实现已经足够好。

如果需要快速验证编码结果而不运行 Java 代码,可将内容粘贴到 Base64 编码器 中,确认输出是否与代码一致。

常见问题解答

如何在 Java 中对 String 进行 Base64 编码?

先用 getBytes(StandardCharsets.UTF_8) 将字符串转换为字节,再将字节数组传入 Base64.getEncoder().encodeToString()。务必显式指定 UTF-8——不带字符集参数调用 getBytes() 会使用平台默认编码,该编码因操作系统和 JVM 配置而异。

Java
import java.util.Base64;
import java.nio.charset.StandardCharsets;

String payload = "grant_type=client_credentials&scope=read:metrics";
String encoded = Base64.getEncoder()
    .encodeToString(payload.getBytes(StandardCharsets.UTF_8));
// Z3JhbnRfdHlwZT1jbGllbnRfY3JlZGVudGlhbHMmc2NvcGU9cmVhZDptZXRyaWNz

Base64.getEncoder() 和 Base64.getUrlEncoder() 有什么区别?

两者都进行 Base64 编码,但 getUrlEncoder() 使用 RFC 4648 第 5 节定义的 URL 安全字母表。它将 + 替换为 -,将 / 替换为 _,使输出可直接出现在 URL 和文件名中而无需百分号编码。标准编码器使用 + 和 /,这两个字符与 URL 查询参数和路径段存在冲突。

Java
byte[] data = "subject=usr_7b3c&role=admin".getBytes(StandardCharsets.UTF_8);

String standard = Base64.getEncoder().encodeToString(data);
// c3ViamVjdD11c3JfN2IzYyZyb2xlPWFkbWlu

String urlSafe = Base64.getUrlEncoder().encodeToString(data);
// c3ViamVjdD11c3JfN2IzYyZyb2xlPWFkbWlu
// (此处相同,但当出现 + → - 和 / → _ 的字符时会有差异)

Java 8 和 Java 17 中的 java.util.Base64 是否相同?

是的。java.util.Base64 API 自 Java 8 引入以来从未更改。该类、其嵌套的 Encoder 和 Decoder 类,以及所有工厂方法(getEncoder、getUrlEncoder、getMimeEncoder)在 Java 8、11、17 和 21 上完全一致。升级 JDK 版本时无需迁移或修改代码。

Java
// 以下代码在 Java 8 到 Java 21+ 上编译和运行结果完全一致
import java.util.Base64;
import java.nio.charset.StandardCharsets;

String encoded = Base64.getEncoder()
    .encodeToString("stable-api".getBytes(StandardCharsets.UTF_8));
System.out.println(encoded);  // c3RhYmxlLWFwaQ==

如何在 Java 中将文件编码为 Base64?

用 Files.readAllBytes(Path) 将文件读取为字节数组,再传入 Base64.getEncoder().encodeToString()。对于不应全部加载进内存的大文件,使用 Base64.getEncoder().wrap(OutputStream) 以流式方式输出编码结果。

Java
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.Base64;

byte[] fileBytes = Files.readAllBytes(Path.of("config/tls-cert.pem"));
String encoded = Base64.getEncoder().encodeToString(fileBytes);

为什么 sun.misc.BASE64Encoder 被废弃?

sun.misc.BASE64Encoder 是 JDK 内部类,从未属于公共 API。它位于 sun.misc 包中,Oracle 明确警告不要使用该包。Java 8 引入了 java.util.Base64 作为官方公共替代方案。自 Java 9 和模块系统起,访问 sun.misc 类会产生警告,某些 JDK 配置下甚至会报错。

Java
// 旧方式——请勿使用,已在现代 JDK 中移除
// import sun.misc.BASE64Encoder;
// String encoded = new BASE64Encoder().encode(data);

// Java 8 起的正确方式
import java.util.Base64;
String encoded = Base64.getEncoder().encodeToString(data);

如何在 Java 中进行 Base64 编码与解码的往返验证?

用 Base64.getEncoder().encodeToString(bytes) 编码,用 Base64.getDecoder().decode(encodedString) 解码。再用 new String(bytes, StandardCharsets.UTF_8) 将解码后的字节数组还原为字符串。只要 getBytes() 和 new String() 使用相同的字符集,往返过程就能完整保留原始数据。

Java
import java.util.Base64;
import java.nio.charset.StandardCharsets;

// 编码
String original = "session_token=eyJhbGciOiJSUzI1NiJ9";
byte[] originalBytes = original.getBytes(StandardCharsets.UTF_8);
String encoded = Base64.getEncoder().encodeToString(originalBytes);

// 解码
byte[] decodedBytes = Base64.getDecoder().decode(encoded);
String decoded = new String(decodedBytes, StandardCharsets.UTF_8);

System.out.println(original.equals(decoded));  // true

相关工具

  • Base64 Decoder将 Base64 字符串还原为原始文本或二进制格式——编码操作的逆向工具。
  • URL Encoder对字符串进行百分号编码以安全用于 URL——与 Base64 URL 安全编码不同,但常配合使用。
  • JWT Decoder解析 JWT 令牌——其 header 和 payload 段是 Base64url 编码的 JSON,无需库即可解码。
  • JSON Formatter在 Base64 编码前后对 JSON 载荷进行格式化——调试 API 集成时非常实用。
也支持:JavaScriptPython
AO
Aisha OseiJava Security & API Engineer

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.

PN
Pavel Novak技术审阅者

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.