Base64
Base64 is a binary-to-text encoding scheme that represents arbitrary byte data using only 64 printable ASCII characters (A–Z, a–z, 0–9, plus two symbols), making binary data safe to embed in contexts that only support text — email bodies (MIME), URLs, JSON payloads, HTTP headers (Basic Authentication), and embedded data URIs. Java exposed Base64 encoding only through ad-hoc, inconsistent mechanisms before Java 8 (e.g. the internal, unsupported sun.misc.BASE64Encoder, or pulling in javax.xml.bind.DatatypeConverter, or third-party libraries like Apache Commons Codec); java.util.Base64, introduced in Java 8, provides a standard, supported, and notably fast encoder/decoder directly in the JDK. This entry covers why Base64 encoding exists and what problem it solves, the three Base64 variants Java supports (Basic, URL-safe, MIME), the streaming encoder/decoder APIs, and common pitfalls like confusing encoding with encryption.
Motivation — Why Binary Data Needs Text-Safe Encoding
// ── The problem: raw bytes don't survive text-only transports/formats ──
byte[] imageBytes = readImageFile(); // arbitrary binary data
// Cannot safely embed imageBytes directly inside a JSON string field,
// an HTTP header, or a URL query parameter — many byte values are illegal
// or will be altered/stripped by intermediate systems.
// ── Pre-Java 8: inconsistent, unsupported approaches ────────────────────
// sun.misc.BASE64Encoder encoder = new sun.misc.BASE64Encoder(); // internal, unsupported API
// String encoded = encoder.encode(imageBytes); // triggers warnings
// javax.xml.bind.DatatypeConverter.printBase64Binary(imageBytes); // tied to JAXB, removed by default in Java 11+
// ── Java 8+: standard, supported API ────────────────────────────────────
String encoded = Base64.getEncoder().encodeToString(imageBytes);
byte[] decoded = Base64.getDecoder().decode(encoded);
// encoded is now plain ASCII text — safe to put in JSON, XML, or most text protocolsBasic, URL-Safe, and MIME Variants
byte[] data = "Hello, World! /+special?".getBytes(StandardCharsets.UTF_8);
// ── Basic — general purpose, single line, standard alphabet ────────────
String basic = Base64.getEncoder().encodeToString(data);
System.out.println(basic); // contains possible '+' and '/' characters
// ── URL-safe — for embedding directly in URLs/query params ─────────────
String urlSafe = Base64.getUrlEncoder().encodeToString(data);
System.out.println(urlSafe); // '+' -> '-', '/' -> '_' — safe in URL paths/queries
String url = "https://example.com/download?token=" + urlSafe;
// ── Without padding ─────────────────────────────────────────────────────
String noPad = Base64.getUrlEncoder().withoutPadding().encodeToString(data);
// omits trailing '=' characters
// ── MIME — line-wrapped at 76 chars, CRLF separated, for email content ─
byte[] largeData = new byte[200];
new Random().nextBytes(largeData);
String mime = Base64.getMimeEncoder().encodeToString(largeData);
System.out.println(mime); // contains embedded \r\n every 76 chars
// ── Decoding must match the encoding variant used ───────────────────────
byte[] decodedBasic = Base64.getDecoder().decode(basic);
byte[] decodedUrl = Base64.getUrlDecoder().decode(urlSafe);
// Base64.getDecoder().decode(urlSafe) would FAIL or produce wrong bytes —
// the Basic decoder doesn't understand '-' and '_' substitutionsStreaming API and the Common Pitfall: Encoding Is Not Encryption
// ── Streaming encode while writing to a file ────────────────────────────
try (OutputStream fileOut = Files.newOutputStream(Path.of("output.b64"));
OutputStream encodedOut = Base64.getEncoder().wrap(fileOut)) {
encodedOut.write(largeBinaryData); // encodes on the fly, no full buffering required
}
// ── Streaming decode while reading from a file ───────────────────────────
try (InputStream fileIn = Files.newInputStream(Path.of("output.b64"));
InputStream decodedIn = Base64.getDecoder().wrap(fileIn)) {
byte[] original = decodedIn.readAllBytes(); // decodes on the fly
}
// ── PITFALL: Base64 is NOT encryption — fully reversible, no secret ────
String password = "mySecretPassword123";
String encoded = Base64.getEncoder().encodeToString(password.getBytes());
// Anyone can reverse this with zero knowledge of any key:
String revealed = new String(Base64.getDecoder().decode(encoded));
System.out.println(revealed); // "mySecretPassword123" — fully recovered, no secret needed
// HTTP Basic Auth — Base64 only obscures, TLS provides the actual protection:
String credentials = "user:password";
String authHeader = "Basic " + Base64.getEncoder().encodeToString(credentials.getBytes());
// Without HTTPS, this header is as exposed on the wire as plaintext "user:password"
// Correct approach for actually protecting a password — hash with a salt, e.g.:
// String hashed = someStrongPasswordHasher.hash(password); // NOT Base64 — irreversible by design