Skip to content
Java java8 5 min read

Base64 Encode/Decode

Base64 is a way to represent binary data — images, files, tokens, encrypted bytes — as plain ASCII text so it travels safely through channels that only handle text (HTTP headers, JSON fields, email bodies). Java 8 introduced java.util.Base64, a clean, built-in API that replaces older workarounds and covers every common encoding flavor.

Why Base64?

Binary bytes such as 0xFF or 0x00 can get corrupted or misread when pasted into a URL or an email. Base64 maps every 3 bytes of input into 4 printable characters from a 64-character alphabet (A-Z, a-z, 0-9, +, /). The result is about 33 % larger than the original, but it is completely safe to store and transmit as text.

Note: Base64 is encoding, not encryption. Anyone can decode it instantly. Never rely on it for security.

The Three Flavors

Base64 exposes three nested encoder/decoder pairs:

FlavorFactory methodAlphabet differs?Line breaks?Typical use
BasicBase64.getEncoder()No (+, /)NoGeneral binary-to-text
URL & Filename safeBase64.getUrlEncoder()Yes (-, _)NoURLs, file names, JWTs
MIMEBase64.getMimeEncoder()NoYes (76 chars + CRLF)Email attachments

Basic Encoding and Decoding

import java.util.Base64;

public class BasicBase64 {
    public static void main(String[] args) {
        String original = "Hello, Java 8!";

        // Encode
        String encoded = Base64.getEncoder()
                               .encodeToString(original.getBytes());
        System.out.println("Encoded : " + encoded);

        // Decode
        byte[] decodedBytes = Base64.getDecoder().decode(encoded);
        String decoded = new String(decodedBytes);
        System.out.println("Decoded : " + decoded);
    }
}

Output:

Encoded : SGVsbG8sIEphdmEgOCE=
Decoded : Hello, Java 8!

The trailing = character is padding — Base64 works in groups of 3 bytes, so it pads the last group to reach a multiple of 4 output characters.

Encoding Without Padding

If your target system rejects = padding (some JWT libraries do), strip it:

String noPad = Base64.getEncoder()
                     .withoutPadding()
                     .encodeToString("Java".getBytes());
System.out.println(noPad); // SmF2YQ  (no trailing =)

URL-Safe Encoding

The standard + and / characters are special in URLs. The URL-safe encoder replaces them with - and _:

import java.util.Base64;

public class UrlSafeBase64 {
    public static void main(String[] args) {
        // bytes that produce + or / in standard Base64
        byte[] data = {(byte) 0xFB, (byte) 0xFF, (byte) 0xFE};

        String standard = Base64.getEncoder().encodeToString(data);
        String urlSafe  = Base64.getUrlEncoder().encodeToString(data);

        System.out.println("Standard : " + standard); // +//+
        System.out.println("URL-safe : " + urlSafe);  // -__-
    }
}

Output:

Standard : +//+
URL-safe : -__-

Tip: Always use URL-safe encoding when the Base64 string will appear inside a URL query parameter or path segment, or when building JWTs.

MIME Encoding (For Email / Large Payloads)

MIME Base64 inserts a CRLF (\r\n) line break every 76 characters, which is required by the email standard (RFC 2045):

import java.util.Base64;

public class MimeBase64 {
    public static void main(String[] args) {
        byte[] data = new byte[100]; // 100 zero bytes as a stand-in
        for (int i = 0; i < data.length; i++) data[i] = (byte) i;

        String mime = Base64.getMimeEncoder().encodeToString(data);
        System.out.println(mime);
    }
}

Output:

AAECAwQFBgcICQoLDA0ODxAREhMUFRYXGBkaGxwdHh8gISIjJCUmJygpKissLS4v
MDEyMzQ1Njc4OTo7PD0+P0BBQkNERUZHSElKS0xNTk8=

You can also customize the line length and separator:

Base64.Encoder customMime = Base64.getMimeEncoder(60, new byte[]{'\n'});

Encoding Files and Byte Arrays

You are not limited to strings. Any byte[] can be encoded — images, PDFs, encryption keys, anything:

import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.Base64;

public class FileBase64 {
    public static void main(String[] args) throws IOException {
        // Read any binary file
        byte[] fileBytes = Files.readAllBytes(Path.of("logo.png"));

        // Encode to a Base64 string (e.g. for embedding in HTML/CSS)
        String b64 = Base64.getEncoder().encodeToString(fileBytes);
        System.out.println("First 60 chars: " + b64.substring(0, 60));

        // Decode back to bytes and write
        byte[] restored = Base64.getDecoder().decode(b64);
        Files.write(Path.of("logo_restored.png"), restored);
    }
}

Warning: Embedding large files as Base64 inside JSON or HTML inflates their size by ~33 %. For assets over a few KB, prefer serving them separately.

Wrapping Streams

For very large data, Base64 can wrap InputStream / OutputStream so you never hold the whole encoded payload in memory:

import java.io.*;
import java.util.Base64;

public class StreamBase64 {
    public static void main(String[] args) throws IOException {
        // Encoding to a stream
        ByteArrayOutputStream out = new ByteArrayOutputStream();
        try (OutputStream enc = Base64.getEncoder().wrap(out)) {
            enc.write("Streaming Base64 in Java!".getBytes());
        }
        System.out.println(out.toString()); // SGVsbG8gU3RyZWFtIQ==...

        // Decoding from a stream
        byte[] encoded = out.toByteArray();
        ByteArrayInputStream in = new ByteArrayInputStream(encoded);
        try (InputStream dec = Base64.getDecoder().wrap(in)) {
            System.out.println(new String(dec.readAllBytes()));
        }
    }
}

This is ideal when piping large files through an encoding layer without allocating a single giant buffer. See Java I/O for more on working with streams.

Under the Hood

Alphabet mapping. The encoder splits every 3-byte group into four 6-bit indices (3 × 8 = 24 bits = 4 × 6) and looks each index up in a 64-character table. The decoder does the inverse lookup. The JDK implementation uses a single pre-computed lookup table for each direction — the entire encode/decode cycle is a tight loop of bit-shift and array-index operations with no heap allocation per character.

Padding math. If the input length is not a multiple of 3, the last group is:

  • 1 remaining byte → 2 Base64 chars + ==
  • 2 remaining bytes → 3 Base64 chars + =

withoutPadding() is zero-cost. It simply skips writing those final = characters; decoding still works because the length of the encoded string implies how many padding bytes existed.

Thread safety. Base64.Encoder and Base64.Decoder instances are stateless and thread-safe — you can safely store them in a static final field and reuse them across threads without synchronization overhead.

Older alternatives. Before Java 8, developers used sun.misc.BASE64Encoder (an internal, unsupported class) or third-party libraries like Apache Commons Codec. The java.util.Base64 API is the definitive replacement — faster, fully supported, and available on all JVMs.

Quick Reference

// Encoder instances (reusable, thread-safe)
Base64.Encoder basic   = Base64.getEncoder();
Base64.Encoder url     = Base64.getUrlEncoder();
Base64.Encoder mime    = Base64.getMimeEncoder();
Base64.Encoder noPad   = Base64.getEncoder().withoutPadding();

// Decoder instances
Base64.Decoder basicDec = Base64.getDecoder();
Base64.Decoder urlDec   = Base64.getUrlDecoder();
Base64.Decoder mimeDec  = Base64.getMimeDecoder();

// Encode a String
String enc = basic.encodeToString("hello".getBytes());

// Decode to a String
String dec = new String(basicDec.decode(enc));
  • Java 8 Features — overview of all Java 8 additions including streams, lambdas, and the new date/time API.
  • Java I/O — byte and character streams that Base64 wraps when processing large files.
  • String Methods — utilities for manipulating the encoded string output.
  • Serialization — a common scenario where serialized byte arrays are Base64-encoded for transport.
  • HttpURLConnection — where Basic Auth credentials are sent as Base64 in the Authorization header.
Last updated June 13, 2026
Was this helpful?