Base64 Decode Java β€” getDecoder().decode() Guide

Β·Backend EngineerΒ·Reviewed byAisha OseiΒ·Published

Use the free online Base64 Decode Online directly in your browser β€” no install required.

Try Base64 Decode Online Online β†’

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.

Before Β· text
After Β· text
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

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
    }
}

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

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
    }
}

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:

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}
    }
}
Note: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

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

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
    }
}
Warning:Never use 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.

Method
Returns
Input Type
Description
getDecoder()
Base64.Decoder
β€”
Standard decoder (RFC 4648 Β§4, + and / alphabet with = padding)
getUrlDecoder()
Base64.Decoder
β€”
URL-safe decoder (RFC 4648 Β§5, - and _ alphabet with = padding)
getMimeDecoder()
Base64.Decoder
β€”
MIME decoder β€” ignores line separators and non-Base64 characters
decode(String src)
byte[]
String
Decodes input string to a new byte array
decode(byte[] src)
byte[]
byte[]
Decodes input byte array to a new byte array
decode(byte[] src, byte[] dst)
int
byte[] + byte[]
Decodes into pre-allocated dst buffer, returns bytes written
wrap(InputStream is)
InputStream
InputStream
Returns a stream that decodes Base64 data on the fly

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.

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...
    }
}
Note: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:

Java 11+
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:

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());
        }
    }
}
Note:Wrap the decode call in its own try-catch for 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.

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

For 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.

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}
    }
}

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.

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);
        }
    }
}

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).

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+
        }
    }
}
Warning: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:

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"]}
    }
}

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.

❌ Using getDecoder() on URL-safe input

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().

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"}
❌ Not specifying charset when constructing String

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.

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
❌ Decoding a string with trailing whitespace

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.

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
❌ Converting binary data to String

Problem: 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.

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);

Method Comparison

The built-in decoders cover most use cases. Apache Commons Codec and Guava are alternatives you might encounter in older codebases.

Method
Encoding Variant
Ignores Whitespace
Streaming
Custom Types
Requires Install
Base64.getDecoder()
Standard (+, /)
βœ—
βœ—
βœ—
No (JDK 8+)
Base64.getUrlDecoder()
URL-safe (-, _)
βœ—
βœ—
βœ—
No (JDK 8+)
Base64.getMimeDecoder()
MIME (line breaks OK)
βœ“
βœ—
βœ—
No (JDK 8+)
decoder.wrap(InputStream)
Any variant
Depends on decoder
βœ“
βœ—
No (JDK 8+)
Apache Commons Base64InputStream
Standard / URL-safe
βœ“
βœ“
βœ—
Yes (commons-codec)
Apache Commons Base64.decodeBase64()
Standard
βœ“
βœ—
βœ—
Yes (commons-codec)
Guava BaseEncoding.base64().decode()
Standard
βœ—
βœ—
βœ—
Yes (guava)

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().

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

What 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.

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

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.

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"}

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.

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);
}

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.

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);

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).

  • 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.
Also available in: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 OseiTechnical Reviewer

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.