Codificación Base64 en Kotlin: una guía completa
La mitad de toda conversación sobre Base64 va de leer datos empaquetados de vuelta a bytes. La otra mitad, la razón por la que estás en el sitio correcto, va de producir ese dato empaquetado en primer lugar. En algún sitio de tu aplicación hay bytes que necesitan viajar por un canal que solo entiende texto: una cadena JSON, un header HTTP, un email, una URL, un archivo de configuración. Base64 es la respuesta clásica, y Kotlin tiene una respuesta de primera clase en la biblioteca estándar: la clase Base64 en kotlin.io.encoding, estable desde Kotlin 2.2.
Esta guía recorre lo que de verdad necesitas cuando eres tú quien produce Base64: los cuatro esquemas predefinidos, la perilla de padding, las reglas de salto de línea que rigen el email y los certificados, el alfabeto URL-safe, de dónde vienen los bytes, y un conjunto de escenarios reales con Kotlin en cada uno. El formato en sí, cómo 3 bytes se vuelven 4 caracteres, de dónde sale el =, está cubierto en la página de inicio, así que aquí vamos directos al Kotlin.
Una clase, cuatro preajustes
Toda la API es una sola clase, Base64, en el paquete kotlin.io.encoding. No hay ningún objeto encoder que construir y ningún builder. En su lugar, la clase llega con cuatro instancias predefinidas, una por esquema de RFC, y un objeto companion que hace de sustituto silencioso de la más común:
| Instancia | Alfabeto | Salto de línea al codificar | Padding al codificar | Úsala para |
|---|---|---|---|---|
Base64.Default | + y / | ninguno | emite = | uso general, APIs, data URLs |
Base64.UrlSafe | - y _ | ninguno | emite = (apágalo) | URLs, tokens, JWTs |
Base64.Mime | + y / | CRLF cada 76 caracteres | emite = | cuerpos y adjuntos de email |
Base64.Pem | + y / | CRLF cada 64 caracteres | emite = | certificados y claves privadas |
El detalle de nombres que hace tropezar a la gente: esto son instancias, no fábricas. Cada instancia es un valor inmutable, y cambiar su comportamiento, como el padding, devuelve una instancia nueva en vez de mutar la vieja. Eso hace que los preajustes sean seguros para compartir entre hilos y guardar en objetos, y es por eso que la clase entera puede ser un tipo de valor simple sin estado interno.
Tu primera codificación: bytes entran, cadena sale
Aquí tienes el programa útil más pequeño del sitio: cinco bytes entran, una cadena de ocho caracteres sale. La entrada siempre es un ByteArray (o un trozo de uno), y el resultado es una String normal que puedes poner donde se permita texto:
import kotlin.io.encoding.Base64
fun main() {
val bytes = "Hello".encodeToByteArray()
val packed = Base64.encode(bytes)
println(packed) // SGVsbG8=
}
Esa línea hace más trabajo del que parece. Kotlin te da varias formas de la misma operación, y todas se leen como dice la función:
encode(bytes)devuelve unaString, la forma de arriba.encodeToByteArray(bytes)devuelve unByteArrayde caracteres ASCII, práctico cuando la forma empaquetada va a meterse en otro buffer.encodeIntoByteArray(bytes, destination)escribe en unByteArrayque ya has asignado, lo que ahorra una asignación en los caminos calientes.encodeToAppendable(bytes, builder)añade a cualquier cosa que implementeAppendable, como unaStringBuilder, que es la opción natural cuando estás ensamblando un documento más grande.
Las cuatro aceptan el mismo rango opcional de startIndex y endIndex, así que puedes empaquetar un trozo de un buffer grande sin copiarlo primero. Como Base64.Default es el objeto companion, también puedes soltar la instancia y escribir Base64.encode(bytes) como azúcar; ambas formas son la misma llamada.
La regla 4/3: ¿qué tan largo se pone?
Antes de soltar un codificador, vale la pena saber exactamente cuánto más grande será la salida, porque Base64 gasta caracteres en información que ya tenía. La matemática es estricta: cada 3 bytes de entrada se vuelven exactamente 4 caracteres de salida, así que cualquier resto de 1 o 2 bytes consume igualmente un grupo completo de 4, rellenado con = para completar el grupo. El resultado para los primeros tamaños:
| Bytes de entrada | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 |
|---|---|---|---|---|---|---|---|---|
| Caracteres de salida | 4 | 4 | 4 | 8 | 8 | 8 | 12 | 12 |
La fórmula detrás de la tabla es 4 * ceil(bytes / 3). En el peor caso, un solo byte se vuelve 4 caracteres, un recargo del 300 por ciento; a partir de tres bytes converge a aproximadamente un tercio más de datos en el cable. Ese es el modelo de coste entero; no hay variación por instancia, y por eso deberías ser deliberado al meter payloads grandes por Base64 en vez de recurrir a él por reflejo.
El padding es una configuración, no un destino
En el lado de la codificación, los caracteres = son una decisión de política, y Kotlin la convierte en de primera clase. Cada instancia lleva un PaddingOption, y withPadding te entrega una instancia nueva con la perilla movida. Los cuatro preajustes arrancan en PRESENT, por eso "Hello" sale como SGVsbG8= y no como SGVsbG8:
import kotlin.io.encoding.Base64
fun main() {
val bytes = "Hello".encodeToByteArray()
val noPad = Base64.Default.withPadding(Base64.PaddingOption.ABSENT)
println(Base64.encode(bytes)) // SGVsbG8=
println(noPad.encode(bytes)) // SGVsbG8
}
Hay cuatro posiciones en la perilla. La primera palabra del nombre decide qué emite el codificador; la segunda mitad decide qué estricto será el decodificador de la misma instancia cuando tú (o el otro lado) lo gire después:
| PaddingOption | El codificador emite = | El decodificador acepta = |
|---|---|---|
PRESENT | sí | obligatorio, lo demás falla |
ABSENT | no | prohibido, un pad suelto falla |
PRESENT_OPTIONAL | sí | de las dos maneras |
ABSENT_OPTIONAL | no | de las dos maneras |
La elección más común al codificar es ABSENT con el alfabeto UrlSafe, que es exactamente la forma que esperan los JSON Web Tokens y muchos esquemas de URL. Volverás a encontrarla en un momento.
Base64url: URLs, tokens y JWTs
El alfabeto clásico contiene + y /, y ambos son desastres en las URLs: un + en una query string se lee rutinariamente como espacio, y un / es el separador de rutas. RFC 4648 sección 5 define la variante URL-safe, que pone - y _ en su lugar, y Base64.UrlSafe es ese esquema. Codificar los mismos bytes que producen una / en el alfabeto clásico muestra el intercambio en acción:
import kotlin.io.encoding.Base64
fun main() {
val bytes = "Hello?".encodeToByteArray()
println(Base64.encode(bytes)) // SGVsbG8/
println(Base64.UrlSafe.encode(bytes)) // SGVsbG8_
}
El usuario real canónico es un JWT, cuyo header y payload son base64url sin padding, unidos por puntos. Aquí tienes la mitad de codificación para construir uno, una forma que deberías entender aunque una biblioteca firme el token final:
import kotlin.io.encoding.Base64
fun main() {
val header = """{"alg":"HS256","typ":"JWT"}"""
val payload = """{"sub":"1234567890","name":"John Doe"}"""
val noPad = Base64.UrlSafe.withPadding(Base64.PaddingOption.ABSENT)
val h = noPad.encode(header.encodeToByteArray())
val p = noPad.encode(payload.encodeToByteArray())
val token = "$h.$p.Ym9nVXNlZlNpZ25hdHVyZUZvckRlbW8"
println(token)
}
Imprime:
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIn0.Ym9nVXNlZlNpZ25hdHVyZUZvckRlbW8
Dos avisos corresponden a este sitio. Primero, el tercer segmento es una firma, y producir una auténtica requiere criptografía de verdad (un firmante JCA/JCE o una biblioteca de JWT), nunca bytes hechos a mano; el fragmento de arriba solo demuestra la forma de la codificación. Segundo, si estás en el JVM y llegas a java.util.Base64.getUrlEncoder() por costumbre, fíjate que con ella el padding va por defecto, así que la salida estilo JWT necesita .withoutPadding() allí; el preajuste de Kotlin también emite padding de fábrica con la misma estridencia, y optas fuera con una llamada a withPadding en su lugar.
Salto de línea: los preajustes Mime y Pem
Dos de los cuatro preajustes envuelven su salida en líneas cortas, y la razón es histórica. Los viejos transportes de email destrozaban las líneas largas, así que RFC 2045 sección 6.8 limita el base64 MIME a 76 caracteres por línea; las herramientas de PKI, siguiendo la tradición PEM más antigua, usan 64. Kotlin mete ambas reglas en el propio preajuste: el separador de línea es CRLF, el salto cae exactamente en el límite, y no hay separador final al terminar. Un payload de 200 bytes por cada envoltorio se ve así:
import kotlin.io.encoding.Base64
fun main() {
val data = ByteArray(200) { (it % 251).toByte() }
println(Base64.Mime.encode(data).lines().maxOf { it.length }) // 76
println(Base64.Pem.encode(data).lines().maxOf { it.length }) // 64
}
Para esa entrada, Mime produce 4 líneas y Pem produce 5. La primera línea de Mime es:
AAECAwQFBgcICQoLDA0ODxAREhMUFRYXGBkaGxwdHh8gISIjJCUmJygpKissLS4vMDEyMzQ1Njc4
La trampa que hay que recordar es la dirección opuesta a la para la que escribes código: la salida envuelta no es una sola línea. Si pasas la salida de Mime a un consumidor estricto de línea única, los CRLF se convierten en un error de decodificación, así que elige el envoltorio por canal, no por comodidad. Para APIs, data URLs y cualquier cosa moderna, Default es el defecto correcto, y el salto de línea es historia de email y certificados.
¿De dónde salen los bytes?
Un codificador es tan honesto como los bytes que le pasas, y las decisiones interesantes ocurren un paso antes de que se llame a encode. La fuente más común es el texto, y el error más común es dejar que el charset decida en silencio:
text.encodeToByteArray()es siempre UTF-8, en todas las plataformas. Es la elección correcta para JSON, emails y datos web, y la equivocada si el texto es Latin-1 o UTF-16 y el otro lado decodifica en consecuencia.- En el JVM puedes elegir explícitamente con la extensión inline
text.toByteArray(charset), que está en la biblioteca estándar desde Kotlin 1.0 y es la respuesta del lado Kotlin algetBytes(charset)de Java. Enkotlin.Stringno haygetBytes, así que si escribestext.getBytes()sobre una cadena Kotlin, el compilador te lo dirá; la extensión es el camino.
import kotlin.io.encoding.Base64
fun main() {
val text = "héllo"
println(Base64.encode(text.encodeToByteArray())) // aMOpbGxv
println(Base64.encode(text.toByteArray(Charsets.ISO_8859_1))) // aOlsbG8=
}
Mismas cinco letras, dos formas empaquetadas distintas, porque los bytes eran distintos antes de que Base64 entrara en juego. Si el decodificador luego asume UTF-8, la versión Latin-1 se decodifica en mojibake, y ningún truco Base64 en ningún extremo puede reparar un desajuste de charset.
Las demás fuentes de bytes siguen la misma forma. Un archivo es file.readBytes() o path.readBytes() y luego encode. Un buffer pre-asignado usa encodeIntoByteArray(bytes, destination). Un documento en construcción usa encodeToAppendable(bytes, builder), que devuelve el destino para que las llamadas encadenen como métodos builder:
import kotlin.io.encoding.Base64
fun main() {
val sb = StringBuilder("prefix-")
Base64.encodeToAppendable("Hello".encodeToByteArray(), sb)
println(sb) // prefix-SGVsbG8=
}
Y en el JVM hay una forma en streaming para entradas que no caben en memoria, todavía marcada como experimental e importable bajo su propio nombre. El giro en el nombre: encodingWith envuelve un stream de salida, así que las escrituras a través de él salen como base64, y los bytes base64 caen en el stream subyacente:
import java.io.ByteArrayOutputStream
import kotlin.io.encoding.Base64
import kotlin.io.encoding.ExperimentalEncodingApi
import kotlin.io.encoding.encodingWith
@OptIn(ExperimentalEncodingApi::class)
fun main() {
val raw = ByteArray(10_000) { (it % 251).toByte() }
val packed = ByteArrayOutputStream()
packed.encodingWith(Base64.Default).use { encoded ->
encoded.write(raw)
}
println(packed.size()) // 13336
}
La regla general: encode en memoria para todo lo que cabe, encodingWith para los streams que no caben, y un charset explícito siempre que los bytes sean de verdad texto.
Notas de campo: HTTP Basic Auth
La autenticación HTTP Basic es el caso de uso Base64 más antiguo de internet, y sigue estando en todas partes en el tráfico entre servicios. RFC 7617 define el esquema: toma el usuario y la contraseña, únelos con un solo signo de dos puntos, pon el resultado en base64 y envíalo como Basic más un espacio más la cadena empaquetada en el header Authorization. En Kotlin:
import kotlin.io.encoding.Base64
fun main() {
val credentials = "alice:s3cr3t"
val header = "Basic " + Base64.encode(credentials.encodeToByteArray())
println(header) // Basic YWxpY2U6czNjcjN0
}
¿Por qué Base64 aquí y no algo más fuerte? Porque un valor de header debe ser un único token imprimible, y Base64 lo garantiza. El aviso honesto: Base64 es codificación, no cifrado. Cualquier cliente puede revertir YWxpY2U6czNjcjN0 a alice:s3cr3t en un solo paso, y por eso la autenticación Basic solo pertenece a conexiones TLS, idealmente con credenciales tipo token y no con contraseñas humanas. Cuando parses un header así, parte en los dos puntos exactamente una vez, porque la contraseña puede contener legalmente dos puntos.
Notas de campo: imágenes y data URLs
Los data URLs incrustan activos binarios directamente en HTML, CSS y JSON, de modo que el navegador no haga una segunda petición. La forma es media type, coma, la palabra base64, otra coma, y los bytes empaquetados:
import kotlin.io.encoding.Base64
fun main() {
val png = byteArrayOf(0x89.toByte(), 0x50, 0x4E, 0x47, 0x0D.toByte(), 0x0A.toByte(), 0x1A.toByte(), 0x0A.toByte())
val dataUrl = "data:image/png;base64," + Base64.encode(png)
println(dataUrl) // data:image/png;base64,iVBORw0KGgo=
}
Los bytes de arriba son los primeros ocho de un archivo PNG, el número mágico que comprueba cada decodificador. Por qué encaja Base64: el payload debe ser un token de texto seguro para URL dentro del markup, y Base64 es el único binario-a-texto ampliamente soportado con una gramática estable. La trampa es el tamaño. Un logo de 300 kilobytes se vuelve unos 400 kilobytes de markup, y cada kilobyte extra se paga en cada carga de página que lo incluye. Los data URLs son una gran herramienta para iconos, avatares y sprites pequeños; son una herramienta terrible para vídeo, e incluso mediocre para una fotografía grande. Mide antes de incrustar.
Notas de campo: adjuntos de email
SMTP es un protocolo de texto anterior a cualquier noción de binario, así que cada adjunto de cada email que has recibido en tu vida es Base64, envuelto a 76 caracteres y declarado con el header Content-Transfer-Encoding: base64. Una parte MIME mínima con un adjunto binario pequeño se ve así, con el hueco del cuerpo generado por Kotlin relleno para un header de %PDF- de 5 bytes:
From: sender@example.com
To: receiver@example.com
Subject: report
MIME-Version: 1.0
Content-Type: multipart/mixed; boundary="cut-here"
--cut-here
Content-Type: text/plain; charset="utf-8"
The quarterly report follows as an attachment.
--cut-here
Content-Type: application/pdf
Content-Transfer-Encoding: base64
Content-Disposition: attachment; filename="report.pdf"
JVBERi0=
--cut-here--
(El cuerpo JVBERi0= es el base64 de los cinco bytes %PDF-; un informe real se envolvería en muchas líneas de 76 caracteres.) El lado Kotlin, si tienes los bytes del archivo, es una línea:
import kotlin.io.encoding.Base64
fun main() {
val pdf = byteArrayOf(0x25, 0x50, 0x44, 0x46, 0x2D) // "%PDF-"
println(Base64.Mime.encode(pdf)) // JVBERi0=
}
Las trampas son disciplina de canal. Usa Mime, no Default, para el cuerpo, porque un parser MIME estricto espera el envoltorio, y una línea Default plana de 10.000 caracteres será rechazada o destrozada por algunos transportes. Mantén el valor de la cabecera escrito exactamente como base64 en la línea Content-Transfer-Encoding, y recuerda que el envoltorio es parte del formato: la salida sin envolver y la salida envuelta son representaciones distintas de los mismos bytes, y el parser del otro lado debe saber cuál está comiendo.
Notas de campo: APIs JSON y subidas
Cuando una API quiere binarios en un documento JSON, la convención es un campo de texto que guarda base64, y es uno de los patrones más cómodos del ecosistema porque JSON ya tiene un hogar para el texto. Con kotlinx.serialization la ida y vuelta es directa:
import kotlinx.serialization.Serializable
import kotlinx.serialization.json.Json
import kotlin.io.encoding.Base64
@Serializable
data class UploadRequest(val name: String, val payload: String)
fun main() {
val icon = byteArrayOf(0x89.toByte(), 0x50, 0x4E, 0x47)
val request = UploadRequest("icon.png", Base64.encode(icon))
val json = Json.encodeToString(UploadRequest.serializer(), request)
println(json) // {"name":"icon.png","payload":"iVBORw=="}
}
¿Por qué Base64 aquí? JSON no tiene tipo binario, así que el payload debe ser texto, y Base64 es la gramática binaria menos sorprendente que un consumidor de API reconocerá sin documentación. La trampa es la escala. El recargo 4/3 se paga en cada petición y cada respuesta, y una subida de 10 megabytes se vuelve una cadena JSON de 13,3 megabytes que tu parser tiene que sostener, escapar y validar en memoria. Para archivos grandes, multipart/form-data o un cuerpo binario es casi siempre el formato de cable mejor; reserva el base64-en-JSON para miniaturas, iconos, firmas y blobs pequeños donde la comodidad supera al impuesto.
Notas de campo: configuración y línea de comandos
Los dos patrones finales son los pequeños que aparecen en cada base de código. Valores de configuración, tokens, claves de licencia, a veces secretos pequeños, viajan a menudo por variables de entorno y archivos de propiedades como base64 porque el transporte es solo texto y el valor puede contener comillas o saltos de línea. Leerlos de vuelta es la misma danza de dos pasos a la inversa: System.getenv o una búsqueda de propiedad, y luego decodificar. En la línea de comandos, codificar un archivo para transporte o inspección es un programa de diez líneas:
import java.io.File
import kotlin.io.encoding.Base64
fun main(args: Array<String>) {
require(args.isNotEmpty()) { "usage: b64encode <file>" }
val bytes = File(args[0]).readBytes()
val encoded = Base64.encode(bytes)
File(args[0] + ".b64").writeText(encoded)
println("Wrote ${encoded.length} characters to ${args[0]}.b64")
}
Ejecútalo sobre un archivo que contiene los diez bytes hello file, y escribe 16 caracteres, aGVsbG8gZmlsZQ==. Las trampas en ambos casos son las mismas dos: el Base64 en configuración no es una bóveda, el valor está a un paso del texto plano y debe tratarse como un secreto en el cable de todas formas, y una herramienta CLI hecha a mano debería decidir su alfabeto a propósito, porque un usuario que meta tu salida por tubería en una URL querrá UrlSafe, no Default.
Qué puede salir mal al codificar
La codificación es permisiva con el contenido: cualquier secuencia de bytes es una entrada válida, así que no hay fallo de "símbolo inválido" como el que tienen los decodificadores. Lo que sí lanza es la geometría, y los mensajes son lo bastante precisos como para servirte:
| Situación | Excepción | Mensaje |
|---|---|---|
endIndex más allá del final del array | IndexOutOfBoundsException | startIndex: 0, endIndex: 100, size: 5 |
startIndex más allá de endIndex | IllegalArgumentException | startIndex: 3 > endIndex: 2 |
Array de destino demasiado pequeño para encodeIntoByteArray | IndexOutOfBoundsException | The destination array does not have enough capacity, destination offset: 0, destination size: 2, capacity needed: 8 |
Dos trampas propias de Kotlin se sientan junto a estas. La primera es el clásico error de int + String: bytes.size + " bytes" no compila, porque el plus sobre un Int no concatena cadenas; la forma de interpolación "${bytes.size} bytes" es el camino Kotlin. La segunda es la búsqueda de charset, que lanza UnsupportedCharsetException para un nombre que el JVM no reconoce, como Charset.forName("utf-9"), así que un error tipográfico en un nombre de charset es una excepción en tiempo de ejecución, no un error de compilación, y aparece donde corre el codificador, no donde se escribió el nombre.
Trampas, estilo Kotlin
Las trampas de abajo son las que muerden específicamente a los desarrolladores de Kotlin que llegan a la biblioteca estándar por primera vez:
- Dejar que
encodeToByteArray()elija el charset por ti. Es siempre UTF-8, en silencio, y una fuente Latin-1 o UTF-16 se empaquetará en bytes que el decodificador no podrá leer de vuelta. Decide el charset a propósito, contoByteArray(charset)en el JVM cuando no sea UTF-8. - Llegar a
java.util.Base64por memoria muscular. SugetUrlEncoder()pone padding por defecto, que es la forma equivocada para JWTs a no ser que recuerdes.withoutPadding(); el preajuste de Kotlin hace la elección explícita en los dos lados. - Alimentar un consumidor de línea única con la salida envuelta de
MimeoPem. Los CRLF son parte de la representación y harán fallar a un decodificador estricto que espera una sola línea; envuelve solo cuando el canal espera envoltorio. - Escribir
text.getBytes()sobre una cadena Kotlin. El método de Java no es visible enkotlin.String; la extensión inlinetoByteArray(charset), presente desde Kotlin 1.0, es el reemplazo. - Correr toolchains viejos. El Kotlin de sistema en algunas distribuciones sigue siendo la 1.3, anterior a toda la
Base64de la biblioteca estándar; la clase necesita la 1.8.20 para existir, la 2.0.20 para controlar el padding, y la 2.2 para ser estable. - Tratar Base64 como una capa de seguridad. Es una codificación de transporte con una inversa pública de un solo paso. Lo que sea secreto debe cifrarse antes de empaquetarse, nunca solo empaquetarse.
Elegir bien: una guía rápida de decisión
Cuando dudes, la decisión casi siempre la hace el canal, no el contenido. La versión corta:
Base64.Defaultpara APIs, JSON, data URLs y cualquier cosa que sea efectivamente una línea de texto. La salida con padding es la forma más compatible en el cable.Base64.UrlSafecon paddingABSENTpara tokens, JWTs y cualquier cosa que aterrice en un segmento de URL o un parámetro de consulta.Base64.Mimepara cuerpos y adjuntos de email, donde las líneas de 76 caracteres son un requisito duro del formato.Base64.Pempara certificados y claves privadas, donde las líneas de 64 caracteres es lo que espera cada herramienta de PKI.
Y luego dos hábitos transversales: haz explícito el charset siempre que la entrada sea texto, y echa un ojo al recargo 4/3 para que los payloads grandes se lleven un canal binario y no uno base64.
El camino al estándar
El camino de la biblioteca estándar hacia Base64 es lo bastante reciente como para que te encuentres Kotlin viejo sin él. La clase apareció por primera vez en Kotlin 1.8.20, en abril de 2023, marcada como experimental, con tres instancias y una superficie más simple: la codificación siempre ponía padding, y no había forma de pedir menos. Si has visto código de la era 1.8 que quita = del final de una cadena con removeSuffix, esa era la única herramienta de la época para la salida sin padding, y es un hábito que vale la pena soltar ahora. Kotlin 2.2, lanzado en junio de 2025, estabilizó la API y añadió la última pieza, la instancia Pem. La perilla de PaddingOption y withPadding ya habían llegado en la línea 2.0, en la 2.0.20 exactamente, la primera forma de primera clase de controlar el padding en las dos direcciones. Los helpers de streaming encodingWith y decodingWith siguen experimentales y solo para JVM, que es cómo la biblioteca estándar marca las APIs de las que quiere más experiencia en campo antes de congelarlas. Desde la línea 2.4.0, el lenguaje también ha pasado a una ventana de soporte de 18 meses para la biblioteca estándar, así que un proyecto fijado a un compilador 2.4.x, como la release estable 2.4.10 que es la actual a fecha de escribir esto, se lleva la API completa de Base64 durante toda la vida de ese ciclo de soporte.
Pequeñas maravillas
Unos cuantos detalles que hacen la clase más interesante una vez que los conoces:
Base64.encode(bytes)sin instancia funciona porqueBase64.Defaultestá definido en el objeto companion; el companion es el esquema por defecto, así que el azúcar y la forma con nombre son literalmente el mismo objeto.- La función
encodeToAppendablees estilo builder: devuelve el appendable de destino, así que el patrón documentado es ignorar el valor de retorno y seguir usando tu builder. - El padding nunca llena un grupo entero: una cadena base64 termina con cero, uno o dos caracteres
=, y contar el pad te dice exactamente cuántos bytes sobraban en el último trío del original. Pemes el más nuevo de los cuatro preajustes, se unió en 2.2; el envoltorio de 64 caracteres es una convención de PKI más antigua que la regla de RFC 2045 a la que se le sienta al lado.- En el JVM la biblioteca estándar deliberadamente no delega en
java.util.Base64; las dos implementaciones son separadas, lo que mantiene el comportamiento idéntico entre plataformas al coste de una optimización comentada que el equipo de Kotlin ha mantenido en el árbol para un futuro donde la API de Java lo permita. - La misma release 2.2 que estabilizó
Base64también estabilizóHexFormat, la clase de formato hexadecimal enkotlin.textque era experimental desde Kotlin 1.9, así que las codificaciones de texto a nivel de byte tienen ya un hogar asentado en la biblioteca estándar.
Para rematar, y adónde ir a continuación
Producir Base64 en Kotlin es una lista corta de decisiones deliberadas: elige el preajuste por canal, decide el padding a propósito, mantén el charset explícito cuando la entrada sea texto, y respeta el recargo 4/3 cuando el payload sea grande. Todo lo demás, archivos, buffers, appendables, streams, es un envoltorio fino alrededor de las mismas cuatro instancias. La otra dirección, llevar ese texto empaquetado de vuelta a bytes, tiene sus propias reglas de estricticidad, sus propios modos de fallo y sus propias trampas, y el artículo relacionado en el sitio hermano cubre la decodificación Base64 en Kotlin en profundidad.
Última actualización: 2026-09-08
Artículo relacionado: Decodificación Base64 en Kotlin: una guía completa