Upload a file
  1. base64hub
  2. Base64 in Code
  3. Java
Code

Base64 in Java

Since Java 8, java.util.Base64 handles standard, URL-safe and MIME Base64, including streaming. All outputs below come from Java 21.

Encode and decode a string

java.util.Base64 works with bytes. Convert strings with an explicit charset, because getBytes() without arguments uses the platform default, which was not always UTF-8 before Java 18.

import java.nio.charset.StandardCharsets;
import java.util.Base64;

String enc = Base64.getEncoder()
        .encodeToString("héllo wörld".getBytes(StandardCharsets.UTF_8));
System.out.println(enc);   // aMOpbGxvIHfDtnJsZA==

String dec = new String(Base64.getDecoder().decode(enc), StandardCharsets.UTF_8);
System.out.println(dec);   // héllo wörld

Base64.Encoder and Base64.Decoder instances are thread-safe, so you can keep them in a static final field.

URL-safe Base64 with and without padding

getUrlEncoder() uses - and _. Padding is on by default; withoutPadding() returns a new encoder that omits it:

byte[] d = "<<???>>".getBytes(StandardCharsets.UTF_8);

Base64.getUrlEncoder().encodeToString(d);                   // PDw_Pz8-Pg==
Base64.getUrlEncoder().withoutPadding().encodeToString(d);  // PDw_Pz8-Pg

new String(Base64.getUrlDecoder().decode("PDw_Pz8-Pg"), StandardCharsets.UTF_8);
// <<???>>

The decoders accept input with or without padding, so getDecoder().decode("aGk") also works and returns hi. JWT segments are unpadded Base64URL; see Base64 vs Base64URL for when to use which.

MIME encoder and decoder

getMimeEncoder() writes lines of 76 characters separated by \r\n, as RFC 2045 requires. It does not add a line break after the last line.

String mime = Base64.getMimeEncoder().encodeToString("x".repeat(60).getBytes());
// eHh4eHh4...eHh4  (76 chars) + "\r\n" + "eHh4"

Base64.getMimeEncoder(64, "\n".getBytes());   // PEM-style 64-char lines

The line length is rounded down to a multiple of 4. getMimeDecoder() is the lenient one: it ignores line separators and any other character outside the alphabet.

Base64.getMimeDecoder().decode("aGVs\r\nbG8K!");   // bytes of "hello\n"

The MIME Base64 encoder produces the same format in the browser.

Encode and decode a file

Small files:

import java.nio.file.*;

byte[] file = Files.readAllBytes(Path.of("hello.txt"));
System.out.println(Base64.getEncoder().encodeToString(file));   // aGVsbG8K

Files.write(Path.of("copy.txt"), Base64.getDecoder().decode("aGVsbG8K"));

Streaming large files with wrap()

Encoder.wrap(OutputStream) and Decoder.wrap(InputStream) encode and decode on the fly, so memory use stays flat no matter how large the file is:

try (InputStream in = new FileInputStream("big.bin");
     OutputStream out = Base64.getEncoder().wrap(new FileOutputStream("big.b64"))) {
    in.transferTo(out);
}

try (InputStream in = Base64.getDecoder().wrap(new FileInputStream("big.b64"));
     OutputStream out = new FileOutputStream("big.out")) {
    in.transferTo(out);
}

Closing the wrapping stream matters: the encoder writes the final group and padding on close(). Forget it and the final group of up to four characters, padding included, is missing. On a 300 KB test file the output matched encodeToString exactly and the round trip was byte-identical. InputStream.transferTo needs Java 9; on Java 8, copy with a buffer loop.

Validation and exceptions

The basic and URL decoders are strict and throw java.lang.IllegalArgumentException. The message tells you what went wrong, with characters printed in hex:

Input to getDecoder()Message
PDw_Pz8-Pg==Illegal base64 character 5f
"aGVs\nbG8K"Illegal base64 character a
aGk=!!Input byte array has incorrect ending byte at 4
aGk==Input byte array has incorrect ending byte at 4
aInput byte[] should at least have 2 bytes for base64 bytes

So validation is just a try/catch:

static boolean isBase64(String s) {
    try {
        Base64.getDecoder().decode(s);
        return true;
    } catch (IllegalArgumentException e) {
        return false;
    }
}

One gap: the decoder accepts non-zero padding bits, so aGl= decodes to hi without complaint. If you need canonical input only, re-encode and compare. The Base64 validator shows which character is the problem, and common Base64 errors explains each class of failure.

Common pitfalls

  • Platform charset. "text".getBytes() and new String(bytes) use the default charset. On Windows with Java 17 or older that was usually Windows-1252, which breaks non-ASCII text. Java 18 made UTF-8 the default (JEP 400), but passing StandardCharsets.UTF_8 costs nothing.
  • Wrong decoder for the input. URL-safe input in the basic decoder gives Illegal base64 character 5f or 2d. MIME input gives Illegal base64 character a or d.
  • encode() vs encodeToString(). encode() returns a byte[] of ASCII characters. Printing that array shows something like [B@1b6d3586, not Base64.
  • Not closing the wrapped stream. The encoder output is truncated without close().
  • Assuming Base64 hides data. It does not; see encoding vs encryption.

Version notes

  • Java 8: java.util.Base64 introduced with basic, URL and MIME variants, withoutPadding() and wrap().
  • Java 9: InputStream.transferTo, used above for stream copies.
  • Java 11: javax.xml.bind.DatatypeConverter.printBase64Binary is gone with the removal of JAXB from the JDK.
  • Java 18: UTF-8 became the default charset.

Kotlin can use all of the above; it also has its own multiplatform API, covered in Base64 in Kotlin.

FAQ

What does 'Illegal base64 character 5f' mean?

The number is the offending character in hex. 5f is _ and 2d is -, which means you are decoding URL-safe Base64 with getDecoder(). Use getUrlDecoder() instead. a is a newline and 20 a space; use getMimeDecoder() or strip whitespace.

Should I still use sun.misc.BASE64Encoder or javax.xml.bind.DatatypeConverter?

No. sun.misc.BASE64Encoder is an internal API that is gone from modern JDKs, and javax.xml.bind was removed from the JDK in Java 11. java.util.Base64 replaces both.

Does the Java decoder accept Base64 without padding?

Yes. Base64.getDecoder().decode("aGk") returns the bytes of hi. The decoders accept missing padding but reject wrong padding.

How do I Base64 encode on Android?

On API level 26+ java.util.Base64 is available. Older code uses android.util.Base64.encodeToString(bytes, Base64.NO_WRAP); without NO_WRAP it adds line breaks.

Keep reading