DEV Community

ToolDeck for ToolDeck

Posted on • Originally published at tooldeck.top

Base64 Decode in Java: getDecoder, getUrlDecoder, getMimeDecoder, and Streaming

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.

Key Takeaways:

  • 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: ZGItcHJvZC51cy1lYXN0LTEuYW1hem9uYXdzLmNvbTo1NDMy
After: 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
    }
}
Enter fullscreen mode Exit fullscreen mode

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
    }
}
Enter fullscreen mode Exit fullscreen mode

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}
    }
}
Enter fullscreen mode Exit fullscreen mode

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

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
    }
}
Enter fullscreen mode Exit fullscreen mode

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
    }
}
Enter fullscreen mode Exit fullscreen mode

⚠️ 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.

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"
            + "aVFvZU1BMEdDU3FHU0liM0RRRUE=";

        // getDecoder() would throw IllegalArgumentException here
        byte[] decoded = Base64.getMimeDecoder().decode(pemBody);
        System.out.println(new String(decoded, StandardCharsets.UTF_8));
        // -----BEGIN CERTIFICATE-----
        // MIIBozCCAUigAwIBAgIJAIpaT2T...
    }
}
Enter fullscreen mode Exit fullscreen mode

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:

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());
        }
    }
}
Enter fullscreen mode Exit fullscreen mode

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());
        }
    }
}
Enter fullscreen mode Exit fullscreen mode

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.

# 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
Enter fullscreen mode Exit fullscreen mode

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.

<!-- pom.xml -->
<dependency>
    <groupId>commons-codec</groupId>
    <artifactId>commons-codec</artifactId>
    <version>1.17.0</version>
</dependency>
Enter fullscreen mode Exit fullscreen mode
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}
    }
}
Enter fullscreen mode Exit fullscreen mode

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);
        }
    }
}
Enter fullscreen mode Exit fullscreen mode

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+
        }
    }
}
Enter fullscreen mode Exit fullscreen mode

⚠️ 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:

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"]}
    }
}
Enter fullscreen mode Exit fullscreen mode

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 — BROKEN
String header = "eyJhbGciOiJSUzI1NiJ9";
byte[] decoded = Base64.getDecoder().decode(header);
// IllegalArgumentException: Illegal base64 character 2d

// After — FIXED
String header = "eyJhbGciOiJSUzI1NiJ9";
byte[] decoded = Base64.getUrlDecoder().decode(header);
System.out.println(new String(decoded));
// {"alg":"RS256"}
Enter fullscreen mode Exit fullscreen mode

❌ 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 — BROKEN
byte[] decoded = Base64.getDecoder().decode(encoded);
String result = new String(decoded);
// platform-dependent — may corrupt multi-byte characters

// After — FIXED
byte[] decoded = Base64.getDecoder().decode(encoded);
String result = new String(decoded, StandardCharsets.UTF_8);
// consistent across all platforms
Enter fullscreen mode Exit fullscreen mode

❌ 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 — BROKEN
String encoded = System.getenv("DB_PASSWORD_B64"); // "cG9zdGdyZXM=\n"
byte[] decoded = Base64.getDecoder().decode(encoded);
// IllegalArgumentException: Illegal base64 character a

// After — FIXED
String encoded = System.getenv("DB_PASSWORD_B64");
byte[] decoded = Base64.getDecoder().decode(encoded.strip());
System.out.println(new String(decoded, StandardCharsets.UTF_8));
// postgres
Enter fullscreen mode Exit fullscreen mode

❌ 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 — BROKEN
byte[] decoded = Base64.getDecoder().decode(pngBase64);
String imageStr = new String(decoded); // corrupts binary
Files.writeString(Path.of("image.png"), imageStr); // broken file

// After — FIXED
byte[] decoded = Base64.getDecoder().decode(pngBase64);
// Write bytes directly — no String conversion
Files.write(Path.of("image.png"), decoded);
Enter fullscreen mode Exit fullscreen mode

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

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
Enter fullscreen mode Exit fullscreen mode

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.

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
Enter fullscreen mode Exit fullscreen mode

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"}
Enter fullscreen mode Exit fullscreen mode

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);
}
Enter fullscreen mode Exit fullscreen mode

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);
Enter fullscreen mode Exit fullscreen mode

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

Top comments (0)