Base64 decode in Java is something I end up reaching for every few days β pulling secrets out of Kubernetes environment variables, reading binary payloads from REST APIs, inspecting JWT tokens during a debugging session. Java's built-in java.util.Base64 class (since JDK 8) provides three decoder flavors: getDecoder() for standard Base64, getUrlDecoder() for URL-safe input, and getMimeDecoder() for line-wrapped data like email attachments. For a quick one-off check without writing code, ToolDeck's Base64 decoder handles it instantly in your browser. This guide targets Java 8+ and covers all three decoders, streaming with wrap(InputStream), JWT payload extraction, file and API response decoding, Apache Commons Codec as an alternative, and the four mistakes that produce garbage output in production.
- βBase64.getDecoder().decode(s) is the standard approach β built into java.util.Base64 since JDK 8, no dependencies needed.
- βUse getUrlDecoder() for JWT tokens and OAuth payloads β they use the - and _ alphabet, not + and /.
- βgetMimeDecoder() ignores line breaks and whitespace, making it the right choice for email attachments and PEM certificates.
- βdecoder.wrap(InputStream) decodes on the fly for large files without loading everything into memory.
- βThe basic decoder is strict β trailing newlines, spaces, or wrong alphabet characters throw IllegalArgumentException immediately.
What is Base64 Decoding?
Base64 encoding converts binary data into a 64-character ASCII representation so it can travel safely through text-only channels β JSON fields, HTTP headers, XML documents, email bodies. Decoding reverses this: every 4 Base64 characters map back to 3 original bytes. The = padding at the end signals how many bytes were added to fill the last group. Base64 is not encryption β anyone can reverse it. Its purpose is transport safety, not secrecy.
ZGItcHJvZC51cy1lYXN0LTEuYW1hem9uYXdzLmNvbTo1NDMy
db-prod.us-east-1.amazonaws.com:5432
Base64.getDecoder().decode() β The Standard Decoding Method
The java.util.Base64 class was added in JDK 8 and replaced the old sun.misc.BASE64Decoder that everyone used to rely on. No external dependencies needed β just import java.util.Base64 and call Base64.getDecoder().decode(). The method accepts either a String or a byte[] and returns a byte[] of the decoded data.
Minimal working example
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
}
}Always specify StandardCharsets.UTF_8 when constructing the String. The no-arg new String(bytes) constructor uses the platform default encoding, which varies between systems. On a Windows server with Cp1252 as the default, multi-byte UTF-8 characters silently corrupt.
Round-trip verification
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
}
}Decoding into a pre-allocated buffer
The three-argument decode(byte[] src, byte[] dst) overload writes directly into a destination buffer and returns the number of bytes written. This avoids an extra allocation on hot paths:
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() throws IllegalArgumentException if the input contains characters outside the Base64 alphabet (including line breaks and spaces). If your input might have whitespace, switch to getMimeDecoder() or strip it with encoded.strip() before decoding.Decoding Base64 with Non-Standard Types and Custom Objects
Raw byte[] from decode() often needs to become something more specific: a UUID, a serialized Java object, a protobuf message, or a timestamp. The decoder itself always returns bytes β the conversion to domain types is your responsibility.
Base64 to 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 uuidStr = "f47ac10b-58cc-4372-a567-0e02b2c3d479";
UUID original = UUID.fromString(uuidStr);
// 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 to deserialized JSON object with 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 to deserialize untrusted Base64 data. Java deserialization can lead to remote code execution via gadget chains β if the encoded content comes from an external source, parse it as JSON or protobuf instead of using native Java serialization.Base64.Decoder Methods Reference
All methods belong to java.util.Base64 and its inner class Base64.Decoder. The three factory methods on Base64 return different decoder instances; the decode() and wrap() methods live on the Decoder instance.
getMimeDecoder() β Decoding Line-Wrapped and MIME Base64
The basic decoder rejects anything outside the Base64 alphabet β and that includes the \r\n line breaks that MIME-encoded content always contains. Email attachments, PEM certificates, and some older API responses wrap Base64 output at 76 characters per line. getMimeDecoder() silently ignores line separators and any character not in the Base64 alphabet, so it handles this out of the box.
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() is lenient: it skips over invalid characters rather than throwing an exception. This is fine for known MIME data, but it can silently swallow corruption in arbitrary input. Use getDecoder() when you want strict validation.Decode Base64 from a File and API Response
Reading a Base64-encoded file from disk
Binary files (images, certificates, encrypted blobs) are sometimes stored on disk as Base64 text. Read the file, decode, write the 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());
}
}
}Decoding a Base64 field from an HTTP API response
Cloud APIs (AWS KMS, GitHub Contents, Vault) frequently return binary data as Base64 strings inside JSON. Parse the JSON first, then decode the target 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 separately from the network errors. Mixing I/O exceptions with decoding failures makes debugging harder β you want to know immediately whether the API returned bad data or the network failed.Base64 Decoding from the Command Line
You don't always need a Java program. Every Linux and macOS system has a base64 command, and JDK 9+ ships jshell for interactive Java one-liners. For quick inspection during debugging, these are faster than compiling a 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 -DFor pasting encoded strings directly into a browser, ToolDeck's Base64 decoder handles both standard and URL-safe variants without any setup.
High-Performance Alternative: Apache Commons Codec
Java's built-in java.util.Base64 is already well-optimized β JDK 11+ uses intrinsics on x86 for encoding and decoding. For most applications, there is no reason to reach for a third-party library. Apache Commons Codec turns up in older codebases and offers Base64InputStream for streaming decoding with automatic whitespace handling.
<!-- 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}
}
}The main advantage of Commons Codec over the built-in API is its leniency with whitespace by default and its Base64InputStream class that predates Java's decoder.wrap(). If you're on Java 8+, the built-in API covers everything Commons Codec does. I only reach for Commons Codec when the project already depends on it.
Streaming Large Base64 Files with decoder.wrap()
Loading a 200 MB Base64 file with Files.readString() then calling decode() allocates roughly 350 MB of heap: the encoded string plus the decoded byte array. decoder.wrap(InputStream) decodes on the fly, keeping memory usage flat.
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);
}
}
}On Java 9+ you can replace the read loop with in.transferTo(out) β it does the same thing with less code. Use getMimeDecoder().wrap() instead of getDecoder().wrap() if the file might contain line breaks (PEM files, email exports).
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() does not tolerate line breaks in the stream. If the Base64 data has newlines (line-wrapped at 76 chars), use getMimeDecoder().wrap() instead β otherwise the stream silently produces corrupted output or throws at an unpredictable read position.Decode Base64 JWT Payload in Java Without a JWT Library
A JWT is three Base64url-encoded segments joined by dots. The middle segment is the payload β the part you care about during debugging. You can decode it without pulling in jjwt or Nimbus. Split on ., decode the second part with getUrlDecoder(), and parse the resulting 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"]}
}
}Common Mistakes
I've run into every one of these in code reviews, and the first two account for the vast majority of Base64-related production bugs in Java services.
Problem: JWT tokens and OAuth access tokens use the URL-safe alphabet (- and _). Passing them to getDecoder() throws IllegalArgumentException because - is not in the standard Base64 alphabet.
Fix: Check your data source: tokens from auth systems need getUrlDecoder(); MIME attachments need 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"}Problem: new String(bytes) uses the JVM's default charset, which differs between environments. A Linux CI server (UTF-8) and a Windows production host (Cp1252) produce different results for the same bytes.
Fix: Always pass StandardCharsets.UTF_8 as the second argument.
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
Problem: Base64 strings pasted from terminals or read from config files often have trailing newlines. The basic decoder rejects any character outside the Base64 alphabet.
Fix: Call .strip() on the input before decoding, or switch to getMimeDecoder() which ignores 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));
// postgresProblem: Calling new String(decoded) on binary content (images, protobuf, encrypted blobs) produces an invalid String. Converting it back to bytes later silently corrupts the data because the String constructor replaces invalid UTF-8 sequences.
Fix: Keep binary data as byte[] through your entire pipeline. Only convert to String when you know the content is 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);Method Comparison
The built-in decoders cover most use cases. Apache Commons Codec and Guava are alternatives you might encounter in older codebases.
For JWT tokens and modern API payloads: getUrlDecoder(). For email attachments and PEM certificates: getMimeDecoder(). For large files where memory matters: decoder.wrap(InputStream). Everything else: getDecoder(). Apache Commons Codec makes sense only if it is already in your dependency tree.
For quick verification during development, the online Base64 decoder is faster than writing a one-off class.
Frequently Asked Questions
How do I decode a Base64 string in Java?
Import java.util.Base64 and call Base64.getDecoder().decode(encodedString). It returns a byte[] β wrap it with new String(bytes, StandardCharsets.UTF_8) to get readable text. For URL-safe Base64 (used in JWTs), swap getDecoder() for 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-configWhat is the difference between getDecoder() and getMimeDecoder() in Java?
getDecoder() is strict β it rejects any character outside the Base64 alphabet, including line breaks. getMimeDecoder() tolerates line separators (\r\n) and ignores any non-Base64 characters, making it the right choice for decoding email attachments and PEM certificates where the data is line-wrapped at 76 characters.
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
How do I decode a Base64 URL-safe string in Java?
Use Base64.getUrlDecoder().decode(encoded). The URL decoder expects the - and _ alphabet defined in RFC 4648 Β§5 instead of + and /. JWT tokens always use this alphabet. If the padding characters (=) were stripped (common in JWTs), the URL decoder still handles it β Java's URL decoder accepts both padded and unpadded input.
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"}How do I stream-decode a large Base64 file in Java?
Use decoder.wrap(inputStream) to wrap a FileInputStream. The returned InputStream decodes Base64 on the fly as you read bytes, so memory usage stays constant regardless of file size. Pipe it through a BufferedInputStream or straight to Files.copy() for best throughput.
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);
}Why does Base64.getDecoder().decode() throw IllegalArgumentException?
The basic decoder is strict: it rejects line breaks, spaces, and any character outside A-Za-z0-9+/=. Three common causes: the input has trailing newlines (trim it), the input uses URL-safe characters like - and _ (switch to getUrlDecoder()), or the input was line-wrapped at 76 characters (switch to getMimeDecoder()). Always inspect the raw bytes if the error message is unclear.
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);
Can I decode Base64 in Java without java.util.Base64?
Yes, but there is no good reason to on Java 8+. Before Java 8, developers used sun.misc.BASE64Decoder (internal, removed in Java 9+), javax.xml.bind.DatatypeConverter.parseBase64Binary() (removed in Java 11), or Apache Commons Codec. All three are either deprecated or require an extra dependency. Stick with java.util.Base64 β it is faster, it ships with the JDK, and it covers all three variants (basic, URL-safe, MIME).
Related Tools
- Base64 Encoder β encode text or binary data to Base64 in the browser, handy for generating test fixtures to paste into your Java unit tests.
- JWT Decoder β split and decode all three JWT segments at once, with field-by-field payload inspection β faster than writing a Java class when you just need to read a token.
- URL Decoder β percent-decode URL-encoded strings, useful when API responses combine Base64url data with percent-encoded query parameters.
- JSON Formatter β after decoding a Base64 JWT payload or API config, paste the JSON here to pretty-print and validate the structure.