Codificar y decodificar una cadena
java.util.Base64 trabaja con bytes. Convierte las cadenas indicando un juego de caracteres explícito, porque getBytes() sin argumentos usa el predeterminado de la plataforma, que antes de Java 18 no siempre era UTF-8.
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
Las instancias de Base64.Encoder y Base64.Decoder son thread-safe, así que puedes guardarlas en un campo static final.
Base64 URL-safe con y sin relleno
getUrlEncoder() usa - y _. El relleno está activado por defecto; withoutPadding() devuelve un codificador nuevo que lo omite:
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);
// <<???>>
Los decodificadores aceptan entradas con o sin relleno, así que getDecoder().decode("aGk") también funciona y devuelve hi. Los segmentos de un JWT son Base64URL sin relleno; consulta Base64 frente a Base64URL para saber cuándo usar cada uno.
Codificador y decodificador MIME
getMimeEncoder() escribe líneas de 76 caracteres separadas por \r\n, como exige el RFC 2045. No añade un salto de línea después de la última línea.
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
La longitud de línea se redondea hacia abajo a un múltiplo de 4. getMimeDecoder() es el tolerante: ignora los separadores de línea y cualquier otro carácter fuera del alfabeto.
Base64.getMimeDecoder().decode("aGVs\r\nbG8K!"); // bytes of "hello\n"
El codificador Base64 MIME genera el mismo formato en el navegador.
Codificar y decodificar un archivo
Archivos pequeños:
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"));
Archivos grandes en flujo con wrap()
Encoder.wrap(OutputStream) y Decoder.wrap(InputStream) codifican y decodifican sobre la marcha, así que el uso de memoria se mantiene estable sea cual sea el tamaño del archivo:
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);
}
Cerrar el stream envolvente es importante: el codificador escribe el último grupo y el relleno en close(). Si lo olvidas, falta el último grupo de hasta cuatro caracteres, relleno incluido. Con un archivo de prueba de 300 KB, la salida coincidió exactamente con encodeToString y la ida y vuelta fue idéntica byte a byte. InputStream.transferTo necesita Java 9; en Java 8, copia con un bucle y un búfer.
Validación y excepciones
Los decodificadores básico y URL son estrictos y lanzan java.lang.IllegalArgumentException. El mensaje dice qué ha fallado y muestra los caracteres en hexadecimal:
Entrada a getDecoder() | Mensaje |
|---|---|
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 |
a | Input byte[] should at least have 2 bytes for base64 bytes |
Así que validar es solo un try/catch:
static boolean isBase64(String s) {
try {
Base64.getDecoder().decode(s);
return true;
} catch (IllegalArgumentException e) {
return false;
}
}
Un hueco: el decodificador acepta bits de relleno distintos de cero, así que aGl= se decodifica como hi sin quejarse. Si solo quieres entradas canónicas, vuelve a codificar y compara. El validador Base64 muestra qué carácter es el problema, y errores habituales de Base64 explica cada tipo de fallo.
Errores habituales
- El juego de caracteres de la plataforma.
"text".getBytes()ynew String(bytes)usan el juego de caracteres predeterminado. En Windows con Java 17 o anterior solía ser Windows-1252, lo que estropea el texto no ASCII. Java 18 convirtió UTF-8 en el predeterminado (JEP 400), pero pasarStandardCharsets.UTF_8no cuesta nada. - Un decodificador que no corresponde a la entrada. Una entrada URL-safe en el decodificador básico da
Illegal base64 character 5fo2d. Una entrada MIME daIllegal base64 character aod. encode()frente aencodeToString().encode()devuelve unbyte[]de caracteres ASCII. Si imprimes ese array verás algo como[B@1b6d3586, no Base64.- No cerrar el stream envuelto. Sin
close(), la salida del codificador queda truncada. - Creer que Base64 oculta los datos. No los oculta; consulta codificación frente a cifrado.
Notas de versión
- Java 8: se introduce
java.util.Base64con las variantes básica, URL y MIME,withoutPadding()ywrap(). - Java 9:
InputStream.transferTo, usado arriba para copiar streams. - Java 11:
javax.xml.bind.DatatypeConverter.printBase64Binarydesaparece al eliminarse JAXB del JDK. - Java 18: UTF-8 pasa a ser el juego de caracteres predeterminado.
Kotlin puede usar todo lo anterior; además tiene su propia API multiplataforma, que se explica en Base64 en Kotlin.
Preguntas frecuentes
¿Qué significa «Illegal base64 character 5f»?
El número es el carácter problemático en hexadecimal. 5f es _ y 2d es -, lo que significa que estás decodificando Base64 URL-safe con getDecoder(). Usa getUrlDecoder() en su lugar. a es un salto de línea y 20 un espacio; usa getMimeDecoder() o quita los espacios en blanco.
¿Debo seguir usando sun.misc.BASE64Encoder o javax.xml.bind.DatatypeConverter?
No. sun.misc.BASE64Encoder es una API interna que ya no existe en los JDK modernos, y javax.xml.bind se eliminó del JDK en Java 11. java.util.Base64 sustituye a ambas.
¿El decodificador de Java acepta Base64 sin relleno?
Sí. Base64.getDecoder().decode("aGk") devuelve los bytes de hi. Los decodificadores aceptan que falte el relleno, pero rechazan un relleno incorrecto.
¿Cómo codifico en Base64 en Android?
A partir del nivel de API 26 está disponible java.util.Base64. El código antiguo usa android.util.Base64.encodeToString(bytes, Base64.NO_WRAP); sin NO_WRAP, añade saltos de línea.