Encodage Base64 en Kotlin : un guide complet
La moitié de toute conversation sur le Base64 porte sur la lecture des données packées pour en refaire des octets. L'autre moitié, celle pour laquelle vous êtes sur le bon site, porte sur la production de ces données packées. Quelque part dans votre application, il y a des octets qui doivent voyager par un canal qui ne comprend que le texte : une chaîne JSON, un en-tête HTTP, un email, une URL, un fichier de configuration. Le Base64 est la réponse classique, et Kotlin a une réponse de premier ordre dans la bibliothèque standard : la classe Base64 dans kotlin.io.encoding, stable depuis Kotlin 2.2.
Ce guide passe en revue ce dont vous avez vraiment besoin quand c'est vous qui produisez du Base64 : les quatre schémas preset, la molette du padding, les règles d'enveloppement des lignes qui guident l'email et les certificats, l'alphabet URL-safe, d'où viennent les octets, et une série de scénarios réels avec du Kotlin dans chacun. Le format lui-même, comment 3 octets deviennent 4 caractères, d'où vient le =, est couvert sur la page d'accueil, donc ici on va droit au Kotlin.
Une classe, quatre presets
Toute l'API est une seule classe, Base64, dans le paquet kotlin.io.encoding. Il n'y a pas d'objet encoder à construire ni de builder. En revanche, la classe est livrée avec quatre instances preset, une par schéma RFC, et un objet compagnon qui se substitue discrètement au plus courant :
| Instance | Alphabet | Casage des lignes à l'encodage | Padding à l'encodage | L'utiliser pour |
|---|---|---|---|---|
Base64.Default | + et / | aucun | émets = | usage général, API, data URLs |
Base64.UrlSafe | - et _ | aucun | émets = (à désactiver) | URL, jetons, JWT |
Base64.Mime | + et / | CRLF toutes les 76 caractères | émets = | corps et pièces jointes d'email |
Base64.Pem | + et / | CRLF toutes les 64 caractères | émets = | certificats et clés privées |
Le détail de nommage qui coince les gens : ce sont des instances, pas des usines. Chaque instance est une valeur immuable, et modifier son comportement, comme le padding, renvoie une nouvelle instance au lieu de muter l'ancienne. Ça rend les presets sûrs à partager entre threads et à ranger dans des objets, et c'est pour ça que toute la classe peut être un simple type valeur sans état interne.
Votre premier encodage : des octets en entrée, une String en sortie
Voici le plus petit programme utile du site : cinq octets en entrée, une chaîne de huit caractères en sortie. L'entrée est toujours un ByteArray (ou une tranche d'un), et le résultat est une String plain que vous pouvez mettre partout où le texte est autorisé :
import kotlin.io.encoding.Base64
fun main() {
val bytes = "Hello".encodeToByteArray()
val packed = Base64.encode(bytes)
println(packed) // SGVsbG8=
}
Cette ligne fait plus de travail qu'il n'y paraît. Kotlin vous donne plusieurs formes de la même opération, et elles se lisent toutes comme le dit la fonction :
encode(bytes)renvoie uneString, la forme ci-dessus.encodeToByteArray(bytes)renvoie unByteArrayde caractères ASCII, pratique quand la forme packée va elle-même dans un autre tampon.encodeIntoByteArray(bytes, destination)écrit dans unByteArrayque vous avez déjà alloué, ce qui économise une allocation sur les chemins chauds.encodeToAppendable(bytes, builder)ajoute à n'importe quoi qui implémenteAppendable, comme unStringBuilder, ce qui est le fit naturel quand vous assemblez un document plus grand.
Les quatre acceptent la même plage optionnelle startIndex et endIndex, donc vous pouvez packer une tranche d'un grand tampon sans la copier d'abord. Comme Base64.Default est l'objet compagnon, vous pouvez aussi laisser tomber l'instance et écrire Base64.encode(bytes) comme sucre ; les deux formes sont le même appel.
La règle des 4/3 : quelle sera la longueur ?
Avant de livrer un encodeur, il vaut savoir exactement de combien la sortie sera plus grosse, parce que le Base64 dépense des caractères pour de l'information qu'il avait déjà. Le calcul est strict : chaque groupe de 3 octets d'entrée devient exactement 4 caractères de sortie, donc 1 ou 2 octets restants consomment quand même un groupe complet de 4, complété par du padding = pour remplir le groupe. Le résultat pour les premières tailles :
| Octets en entrée | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 |
|---|---|---|---|---|---|---|---|---|
| Caractères en sortie | 4 | 4 | 4 | 8 | 8 | 8 | 12 | 12 |
La formule derrière le tableau est 4 * ceil(bytes / 3). Dans le pire cas, un octet seul devient 4 caractères, une majoration de 300 pour cent ; à partir de trois octets, ça converge vers à peu près un tiers de données en plus sur le fil. C'est le modèle de coût entier ; il n'y a pas de variation par instance, et c'est pour ça qu'il faut être délibéré avant de passer de gros payloads en Base64, au lieu d'y aller par réflexe.
Le padding est un réglage, pas un destin
Côté encodage, les caractères = sont une décision de politique, et Kotlin en fait une décision de premier ordre. Chaque instance porte un PaddingOption, et withPadding vous remet une nouvelle instance avec la molette déplacée. Les quatre presets démarrent sur PRESENT, c'est pour ça que "Hello" sort en SGVsbG8= plutôt qu'en 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
}
Il y a quatre positions sur la molette. Le premier mot du nom décide ce que l'encodeur émet ; la deuxième moitié décide à quel point le décodeur de la même instance sera strict quand vous (ou l'autre côté) la retournez plus tard :
| PaddingOption | L'encodeur émet = | Le décodeur accepte = |
|---|---|---|
PRESENT | oui | obligatoire, le reste échoue |
ABSENT | non | interdit, un pad égaré échoue |
PRESENT_OPTIONAL | oui | les deux |
ABSENT_OPTIONAL | non | les deux |
Le choix le plus courant au moment de l'encodage est ABSENT avec l'alphabet UrlSafe, qui est exactement la forme que les JSON Web Tokens et beaucoup de schémas d'URL attendent. Vous le recroiserez dans un instant.
Base64url : URL, jetons et JWT
L'alphabet classique contient + et /, et les deux sont des catastrophes dans les URL : un + dans une chaîne de requête est régulièrement lu comme une espace, et un / est le séparateur de chemin. La RFC 4648, section 5, définit la variante URL-safe, qui substitue - et _, et Base64.UrlSafe est ce schéma. Encoder les mêmes octets qui produisent un / dans l'alphabet classique montre l'échange en action :
import kotlin.io.encoding.Base64
fun main() {
val bytes = "Hello?".encodeToByteArray()
println(Base64.encode(bytes)) // SGVsbG8/
println(Base64.UrlSafe.encode(bytes)) // SGVsbG8_
}
L'utilisateur canonique du monde réel est le JWT, dont l'en-tête et le payload sont du base64url sans padding, joints par des points. Voici la moitié encodage de sa construction, une forme que vous devriez comprendre même si une bibliothèque signe le jeton 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
Deux avertissements ont leur place ici. D'abord, le troisième segment est une signature, et produire une signature authentique exige de la vraie cryptographie (un signataire JCA/JCE ou une bibliothèque JWT), jamais des octets faits main ; l'extrait ci-dessus ne démontre que la forme de l'encodage. Ensuite, si vous êtes sur la JVM et que vous attrapez java.util.Base64.getUrlEncoder() par habitude, notez qu'il met du padding par défaut, donc une sortie de style JWT a besoin de .withoutPadding() là-bas ; le preset Kotlin met du padding tout aussi franchement tel quel, et vous sortez du padding avec un appel withPadding à la place.
L'enveloppement des lignes : les presets Mime et Pem
Deux des quatre presets cassent leur sortie en lignes courtes, et la raison est historique. Les vieux transports d'email massacraient les longues lignes, donc la RFC 2045, section 6.8, plafonne le base64 MIME à 76 caractères par ligne ; les outils PKI, suivant la tradition PEM plus ancienne, utilisent 64. Kotlin incorpore les deux règles dans le preset lui-même : le séparateur de lignes est CRLF, la cassure tombe exactement à la limite, et il n'y a pas de séparateur de traîne tout à la fin. Un payload de 200 octets passé par chaque wrapper ressemble à ça :
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
}
Pour cette entrée, Mime produit 4 lignes et Pem en produit 5. La première ligne Mime est :
AAECAwQFBgcICQoLDA0ODxAREhMUFRYXGBkaGxwdHh8gISIjJCUmJygpKissLS4vMDEyMzQ1Njc4
Le piège à retenir est dans la direction inverse de celle pour laquelle vous écrivez du code : une sortie enveloppée n'est pas une seule ligne. Si vous donnez une sortie Mime à un consommateur strict d'une seule ligne, les CRLF deviennent une erreur de décodage, donc choisissez le wrapper selon le canal, pas selon la commodité. Pour les API, les data URLs et tout ce qui est moderne, Default est le bon défaut, et l'enveloppement des lignes est une histoire d'email et de certificats.
D'où viennent les octets ?
Un encodeur n'est honnête qu'à hauteur des octets qu'on lui donne, et les décisions intéressantes ont lieu une étape avant l'appel de encode. La source la plus courante est le texte, et l'erreur la plus courante est de laisser le charset décider en silence :
text.encodeToByteArray()est toujours en UTF-8, sur toutes les plateformes. C'est le bon choix pour le JSON, les emails et les données web, et le mauvais si le texte est en Latin-1 ou UTF-16 et que l'autre côté décode en conséquence.- Sur la JVM, vous pouvez choisir explicitement avec l'extension inline
text.toByteArray(charset), présente dans la bibliothèque standard depuis Kotlin 1.0 et qui est la réponse côté Kotlin àgetBytes(charset)de Java. Il n'y a pas degetBytessurkotlin.String, donc si vous écriveztext.getBytes()sur une chaîne Kotlin, le compilateur vous le dira ; l'extension est le chemin.
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=
}
Les mêmes cinq lettres, deux formes packées différentes, parce que les octets étaient différents avant que tout Base64 ne soit de la partie. Si le décodeur suppose plus tard du UTF-8, la version Latin-1 se décode en salade de caractères, et aucun tour de Base64 à aucune des deux extrémités ne répare un désaccord de charset.
Les autres sources d'octets suivent la même forme. Un fichier, c'est file.readBytes() ou path.readBytes() puis encode. Un tampon pré-alloué utilise encodeIntoByteArray(bytes, destination). Un document en cours de construction utilise encodeToAppendable(bytes, builder), qui renvoie la destination pour que les appels s'enchaînent comme des méthodes de builder :
import kotlin.io.encoding.Base64
fun main() {
val sb = StringBuilder("prefix-")
Base64.encodeToAppendable("Hello".encodeToByteArray(), sb)
println(sb) // prefix-SGVsbG8=
}
Et sur la JVM il y a une forme en streaming pour les entrées qui ne tiennent pas en mémoire, encore marquée expérimentale et importable sous son propre nom. Le piège dans le nommage : encodingWith enveloppe un flux de sortie, donc les écritures faites à travers elle sortent en base64, et les octets base64 atterrissent dans le flux sous-jacent :
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 règle simple : encode en mémoire pour tout ce qui tient, encodingWith pour les flux qui ne tiennent pas, et un charset explicite chaque fois que les octets sont vraiment du texte.
Notes de terrain : HTTP Basic Auth
L'authentification HTTP Basic est le cas d'usage Base64 le plus ancien d'Internet, et elle est encore partout dans le trafic service à service. La RFC 7617 définit le schéma : prenez l'utilisateur et le mot de passe, joignez-les avec un seul deux-points, mettez le résultat en base64, et expédiez-le en Basic plus un espace plus la chaîne packée dans l'en-tête 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
}
Pourquoi du Base64 ici et pas quelque chose de plus fort ? Parce qu'une valeur d'en-tête doit être un jeton imprimable unique, et le Base64 le garantit. L'avertissement honnête : le Base64 est un encodage, pas un chiffrement. N'importe quel client peut inverser YWxpY2U6czNjcjN0 en alice:s3cr3t en une seule étape, c'est pour ça que l'auth Basic n'a sa place que sur des connexions TLS, de préférence avec des jetons plutôt que des mots de passe humains. Quand vous parsez un tel en-tête, découpez sur le deux-points exactement une fois, parce que le mot de passe peut légitimement contenir des deux-points.
Notes de terrain : images et data URLs
Les data URLs intègrent les assets binaires directement dans le HTML, le CSS et le JSON pour que le navigateur ne fasse pas de deuxième requête. La forme est un type média, une virgule, le mot base64, une autre virgule, et les octets packés :
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=
}
Les octets ci-dessus sont les huit premiers d'un fichier PNG, le nombre magique que tous les décodeurs vérifient. Pourquoi le Base64 colle : le payload doit être un jeton texte URL-safe dans le balisage, et le Base64 est le seul binaire-vers-texte largement supporté avec une grammaire stable. Le piège, c'est la taille. Un logo de 300 kilo-octets devient environ 400 kilo-octets de balisage, et chaque kilo-octet en plus est payé à chaque chargement de page qui l'inclut. Les data URLs sont un excellent outil pour les icônes, les avatars et les petites sprites ; elles sont un terrible outil pour la vidéo, et même un outil médiocre pour une grande photographie. Mesurez avant d'intégrer.
Notes de terrain : pièces jointes d'email
Le SMTP est un protocole de texte qui précède toute notion de binaire, donc chaque pièce jointe de chaque email que vous avez jamais reçu est du Base64, cassé à 76 caractères, déclaré avec un en-tête Content-Transfer-Encoding: base64. Une partie MIME minimale avec une petite pièce jointe binaire ressemble à ça, avec le corps généré par Kotlin rempli pour un en-tête %PDF- de 5 octets :
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--
(Le corps JVBERi0= est le base64 des cinq octets %PDF- ; un vrai rapport se casserait sur beaucoup de lignes de 76 caractères.) Côté Kotlin, c'est une ligne quand vous avez les octets du fichier :
import kotlin.io.encoding.Base64
fun main() {
val pdf = byteArrayOf(0x25, 0x50, 0x44, 0x46, 0x2D) // "%PDF-"
println(Base64.Mime.encode(pdf)) // JVBERi0=
}
Les pièges, c'est la discipline du canal. Utilisez Mime, pas Default, pour le corps, parce qu'un parseur MIME strict s'attend à l'enveloppement des lignes, et une ligne Default plain de 10 000 caractères sera rejetée ou massacrée par certains transports. Gardez la casse de l'en-tête exactement base64 dans la ligne Content-Transfer-Encoding, et rappelez-vous que l'enveloppement fait partie du format : une sortie non cassée et une sortie cassée sont deux représentations différentes des mêmes octets, et le parseur de l'autre côté doit savoir laquelle il mange.
Notes de terrain : API JSON et uploads
Quand une API veut du binaire dans un document JSON, la convention est un champ chaîne qui contient du base64, et c'est l'un des motifs les plus pratiques de l'écosystème, parce que le JSON a déjà une maison pour le texte. Avec kotlinx.serialization, l'aller-retour est direct :
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=="}
}
Pourquoi du Base64 ici : le JSON n'a pas de type binaire, donc le payload doit être du texte, et le Base64 est la grammaire binaire la moins surprenante qu'un consommateur d'API reconnaîtra sans documentation. Le piège, c'est l'échelle. La majoration 4/3 est payée à chaque requête et à chaque réponse, et un upload de 10 méga-octets devient une chaîne JSON de 13,3 méga-octets que votre parseur doit tenir, échapper et valider en mémoire. Pour les gros fichiers, multipart/form-data ou un corps binaire est presque toujours le meilleur format de fil ; réservez le base64-dans-JSON aux vignettes, aux icônes, aux signatures et aux petits blobs où la commodité l'emporte sur la taxe.
Notes de terrain : configuration et ligne de commande
Les deux derniers motifs sont les petits qui apparaissent dans toutes les bases de code. Les valeurs de configuration, les jetons, les clés de licence, parfois de petits secrets, voyagent souvent en base64 à travers les variables d'environnement et les fichiers de propriétés, parce que le transport est texte uniquement et que la valeur peut contenir des guillemets ou des retours à la ligne. Les relire, c'est la même danse de deux étapes en sens inverse : System.getenv ou une consultation de propriété, puis décodage. Sur la ligne de commande, encoder un fichier pour le transport ou l'inspection, c'est un programme de dix lignes :
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")
}
Exécuté sur un fichier contenant les dix octets hello file, il écrit 16 caractères, aGVsbG8gZmlsZQ==. Les pièges des deux cas sont les mêmes deux : le Base64 dans la config n'est pas un coffre-fort, la valeur est à un pas du texte clair et doit être traitée comme un secret sur le fil dans tous les cas, et un outil CLI fait main devrait décider son alphabet délibérément, parce qu'un utilisateur qui pipe votre sortie dans une URL aura besoin de UrlSafe, pas de Default.
Ce qui peut mal tourner à l'encodage
L'encodage est indulgent sur le contenu : n'importe quelle séquence d'octets est une entrée valide, donc il n'y a pas d'échec du type « symbole invalide » comme côté décodeurs. Ce qui lève, c'est la géométrie, et les messages sont assez précis pour être utiles :
| Situation | Exception | Message |
|---|---|---|
endIndex au-delà de la fin du tableau | IndexOutOfBoundsException | startIndex: 0, endIndex: 100, size: 5 |
startIndex au-delà de endIndex | IllegalArgumentException | startIndex: 3 > endIndex: 2 |
tableau de destination trop petit pour encodeIntoByteArray | IndexOutOfBoundsException | The destination array does not have enough capacity, destination offset: 0, destination size: 2, capacity needed: 8 |
Deux pièges propres à Kotlin se tiennent à côté de ceux-là. Le premier est l'erreur classique int + String : bytes.size + " bytes" ne compile pas, parce que plus sur un Int ne concatène pas des chaînes ; la forme d'interpolation "${bytes.size} bytes" est la façon Kotlin. Le second est la consultation de charset, qui lève UnsupportedCharsetException pour un nom que la JVM ne reconnaît pas, comme Charset.forName("utf-9"), donc une faute de frappe dans un nom de charset est une exception à l'exécution, pas une erreur de compilation, et elle apparaît là où l'encodeur tourne, pas là où le nom a été tapé.
Des pièges, façon Kotlin
Les pièges ci-dessous sont ceux qui piquent spécifiquement les développeurs Kotlin qui s'attaquent à la bibliothèque standard pour la première fois :
- Laisser
encodeToByteArray()choisir le charset à votre place. C'est toujours du UTF-8, en silence, et une source Latin-1 ou UTF-16 se packera en des octets que le décodeur ne lira pas en retour. Décidez le charset exprès, avectoByteArray(charset)sur la JVM quand ce n'est pas du UTF-8. - Attraper
java.util.Base64par réflexe musculaire. SongetUrlEncoder()met du padding par défaut, ce qui est la mauvaise forme pour les JWT à moins de vous souvenir de.withoutPadding(); le preset Kotlin rend le choix explicite des deux côtés. - Donner une sortie
MimeouPemenveloppée à un consommateur d'une seule ligne. Les CRLF font partie de la représentation et feront échouer un décodeur strict qui s'attend à une ligne ; cassez les lignes seulement quand le canal s'attend à l'enveloppement. - Écrire
text.getBytes()sur une chaîne Kotlin. La méthode Java n'est pas visible surkotlin.String; l'extension inlinetoByteArray(charset), présente depuis Kotlin 1.0, est le remplacement. - Exécuter de vieilles chaînes d'outils. Le Kotlin système de certaines distributions est encore en 1.3, qui précède entièrement le
Base64de la bibliothèque standard ; la classe a besoin de 1.8.20 pour exister, de 2.0.20 pour le contrôle du padding, et de 2.2 pour être stable. - Traiter le Base64 comme une couche de sécurité. C'est un encodage de transport avec une inverse publique en une étape. Tout ce qui est secret devrait être chiffré avant d'être packé, jamais seulement packé.
Bien choisir : un guide de décision express
En cas de doute, la décision est presque toujours prise par le canal, pas par le contenu. La version courte :
Base64.Defaultpour les API, le JSON, les data URLs et tout ce qui est effectivement une ligne de texte. La sortie avec padding est la forme la plus compatible sur le fil.Base64.UrlSafeavec le paddingABSENTpour les jetons, les JWT et tout ce qui atterrit dans un segment d'URL ou un paramètre de requête.Base64.Mimepour les corps et pièces jointes d'email, où les lignes de 76 caractères sont une exigence dure du format.Base64.Pempour les certificats et clés privées, où les lignes de 64 caractères sont ce que tous les outils PKI attendent.
Puis deux habitudes transverses : rendez le charset explicite chaque fois que l'entrée est du texte, et gardez un œil sur la majoration 4/3 pour que les gros payloads aient un canal binaire plutôt qu'un canal base64.
Le chemin vers la stabilité
Le chemin de la bibliothèque standard vers Base64 est assez récent pour que vous croisiez des Kotlin plus anciens sans elle. La classe est apparue pour la première fois dans Kotlin 1.8.20 en avril 2023, marquée expérimentale, avec trois instances et une surface plus simple : l'encodage mettait toujours du padding, et il n'y avait pas de façon d'en demander moins. Si vous avez vu du code de l'ère 1.8 qui retire les = de la fin d'une chaîne avec removeSuffix, c'était l'unique outil de l'époque pour une sortie sans padding, et c'est une habitude qui vaut la peine d'être abandonnée maintenant. Kotlin 2.2, sorti en juin 2025, a stabilisé l'API et ajouté la dernière pièce, l'instance Pem. La molette PaddingOption et withPadding étaient déjà arrivées dans la ligne 2.0, dans 2.0.20 pour être exact, la première façon de premier ordre de contrôler le padding dans les deux directions. Les aides de streaming encodingWith et decodingWith restent expérimentales et réservées à la JVM, c'est ainsi que la bibliothèque standard marque les API sur lesquelles elle veut plus d'expérience terrain avant de les geler. Depuis la ligne 2.4.0, le langage est aussi passé à une fenêtre de support de 18 mois pour la bibliothèque standard, donc un projet fixé sur un compilateur 2.4.x, comme la release stable 2.4.10 actuelle au moment où j'écris ces lignes, a l'API complète de Base64 pour toute la durée de ce cycle de support.
De petites merveilles
Quelques détails qui rendent la classe plus intéressante une fois qu'on les connaît :
Base64.encode(bytes)sans instance fonctionne parce queBase64.Defaultest défini sur l'objet compagnon ; le compagnon est le schéma par défaut, donc le sucre et la forme nommée sont littéralement le même objet.- La fonction
encodeToAppendableest de style builder : elle renvoie l'appendable de destination, donc le motif documenté est d'ignorer la valeur de retour et de continuer à utiliser votre builder. - Le padding ne remplit jamais un groupe entier : une chaîne base64 se termine par zéro, un ou deux caractères
=, et compter les pads vous dit exactement combien d'octets l'original avait en reste dans le dernier trio. Pemest le plus récent des quatre presets, arrivé dans 2.2 ; l'enveloppement à 64 caractères est une convention PKI plus vieille que la règle de la RFC 2045 à côté de laquelle il se tient.- Sur la JVM, la bibliothèque standard ne délègue délibérément pas vers
java.util.Base64; les deux implémentations sont séparées, ce qui garde le comportement identique sur toutes les plateformes, au prix d'une optimisation commentée que l'équipe Kotlin a gardée dans l'arbre pour un futur où l'API Java le permettra. - La même release 2.2 qui a stabilisé
Base64a aussi stabiliséHexFormat, la classe de formatage hexadécimal danskotlin.textexpérimentale depuis Kotlin 1.9, de sorte que les encodages textuels au niveau octet ont maintenant une maison fixée dans la bibliothèque standard.
En résumé, et où aller ensuite
Produire du Base64 en Kotlin, c'est une courte liste de choix délibérés : choisissez le preset selon le canal, décidez le padding exprès, gardez le charset explicite quand l'entrée est du texte, et respectez la majoration 4/3 quand le payload est grand. Tout le reste, fichiers, tampons, appendables, flux, est un fin wrapper autour des mêmes quatre instances. L'autre direction, remettre ce texte packé en octets, a ses propres règles de rigueur, ses propres modes d'échec et ses propres pièges, et l'article lié sur le site sœur couvre le décodage Base64 en Kotlin en profondeur.
Dernière mise à jour : 2026-09-08
Article associé : Décodage Base64 en Kotlin : un guide complet