Haben Sie mit dem Base64-Format zu tun? Dann ist diese Website genau das Richtige für Sie! Nutzen Sie unser superpraktisches Online-Tool, um Ihre Daten zu kodieren oder zu dekodieren.

Base64-Dekodierung in Java: Ein vollständiger Leitfaden

Er taucht auf in einem Support-Ticket, einer API-Antwort, einem Kubernetes-Secret oder versteckt mitten in einer URL: ein langer Lauf aus Buchstaben und Ziffern, hier und da ein +, /, - oder _, und vielleicht ein oder zwei =-Zeichen, die am Ende baumeln. Jemand sagt, es sei Base64, und es enthalte etwas, das Sie brauchen: ein Passwort, einen JSON-Payload, ein Zertifikat, ein Foto. Dieser Leitfaden ist das Java-Rezept, um es zurückzubekommen. Kurze Orientierung, denn die Startseite geht auf das Format im Detail ein: Base64 schreibt jeweils drei Bytes der Daten in vier Zeichen um, die aus einem Alphabet von 64 Buchstaben stammen, und hängt am Ende ein oder zwei =-Pads an, wenn das letzte Stück zu kurz ist. Dekodieren ist die schrumpfende Richtung dieses Tauschs: vier Zeichen gehen rein, drei Bytes kommen raus, und das Ergebnis braucht deshalb immer etwa ein Viertel weniger Platz als die Eingabe.

Jetzt die Schlagzeile, und sie ist eine gute. Seit dem 18. März 2014 liefert jeder JDK ein vollständiges Base64-Werkzeugset in der Standardbibliothek mit: java.util.Base64. Kein Download, keine Maven-Koordinate, keine native Bibliothek. Ein Import, sieben Factory-Methoden, drei Alphabete, und dasselbe Verhalten von Java 8 bis zum heutigen Java 26. Alles in diesem Artikel baut auf genau dieser einen Klasse auf.

Eine ehrliche Grenze, bevor wir loslegen: Dies ist die Dekoder-Seite der Geschichte. Sie lernen, den richtigen Dekodierer für das Alphabet auszuwählen, auf das Sie stoßen, die Fehlermeldungen des JDK zu lesen wie ein Arzt einen Scan, Bytes ohne Mojibake in Text umzuwandeln, die PEM-Rüstung abzuwickeln, Multi-Gigabyte-Payloads zu streamen, und die Sicherheitsfallen zu erkennen, die das Format still und leise in den Weg stellt. Die andere Richtung, Bytes in einen String zu packen, bekommt ihren eigenen Leitfaden, und der ist am Ende von diesem verlinkt.

Was Sie schon besitzen

Base64 in Java zu installieren ist die Einzeiler-Antwort, die Sie am Whiteboard geben: "Es ist im JDK." Die Klasse java.util.Base64 ist seit 1.8 Teil des java.base-Moduls, und ihre Javadoc sagt auch 2026 immer noch Since: 1.8. Das Einzige, was Sie installieren, ist ein JDK, jedes Java 8 oder neuer von jedem Anbieter (Oracle, Eclipse Temurin, Amazon Corretto, Zulu) funktioniert, und auf einem Debian-basierten System ist das ein einziger Befehl:

sudo apt install openjdk-17-jdk-headless

Die API ist eine Fabrik: Sie konstruieren nie einen Dekodierer; Sie bitten die Klasse darum. Die sieben Factory-Methoden geben in jede Richtung drei Persönlichkeiten aus, und die Dekoder-Seite sieht so aus:

Factory-Methode Alphabet Charakter Greifen Sie danach, wenn
getDecoder() A-Z a-z 0-9 + / Strikt: lehnt jedes Zeichen außerhalb des Alphabets ab Daten, die Sie selbst erzeugen oder kontrollieren
getUrlDecoder() A-Z a-z 0-9 - _ Strikt, URL-sicheres Alphabet JWTs, Tokens, IDs, alles, was in einer URL geboren wurde
getMimeDecoder() A-Z a-z 0-9 + / Nachsichtig: überspringt jedes Nicht-Alphabet-Zeichen E-Mail, tatsächlich umgebrochene Eingabe, PEM-Körper
getEncoder(), getUrlEncoder(), getMimeEncoder() wie oben Kodierung, das Territorium des Schwester-Leitfadens Wann immer Sie Base64 erzeugen, statt es zu lesen

Drei Eigenschaften der zurückgegebenen Instanzen lohnen es, sie auswendig zu lernen. Erstens sind sie thread-sicher: Die Javadoc sagt, die Instanzen seien "sicher für die Nutzung durch mehrere gleichzeitige Threads", und der Quellcode zeigt, dass die Factory-Methoden bei jedem Aufruf dieselbe geteilte Instanz zurückgeben, also ist Base64.getDecoder() == Base64.getDecoder() wahr. Legen Sie einen Dekodierer in einem statischen Feld an und teilen Sie ihn über Ihren ganzen Service; Sie kopieren dabei nicht einmal irgendetwas. Zweitens sind sie zwischen den Aufrufen zustandslos, es gibt also nichts zurückzusetzen und nichts, um das man sich synchronisieren müsste. Drittens ist es kein sanftes Nichtstun, null zu übergeben, wo ein Byte-Array oder String erwartet wird: Es ist eine NullPointerException, genau wie die Javadoc der Klasse verspricht.

Sie werden in Codebasen immer noch ältere Bibliotheken treffen, also hier eine schnelle Landkarte. Apache Commons Codec (aktuell 1.22.1) trägt seit 1.0 sein eigenes org.apache.commons.codec.binary.Base64 mit, mit einer Builder-API, die die strikt-oder-nachsichtig-Regel, die Zeilenlänge und den Trenner als Regler offenlegt; sie ist das richtige Werkzeug nur, wenn Sie JVMs vor Java 8 unterstützen müssen oder ihre Form-Prüf-Helfer wollen. Guava liefert com.google.common.io.BaseEncoding, einen ebenso fähigen Veteranen, in Big-Data-Stacks immer noch beliebt. Für alles, was auf einer modernen JVM läuft, ist java.util.Base64 die Standardwahl: null Abhängigkeiten, und Community-Benchmarks finden immer wieder, dass es das schnellste der Truppe ist (mehr dazu im Performance-Abschnitt).

Ihren ersten String dekodieren

Neunzig Prozent des Dekodier-Alltags passen in ein paar Zeilen. Hier ist die ganze Zeremonie, mit dem kleinsten Beispiel, das der RFC selbst verwendet, um das Alphabet zu erklären:

import java.nio.charset.StandardCharsets;
import java.util.Base64;
public class FirstDecode {
  public static void main(String[] args) {
    byte[] bytes = Base64.getDecoder().decode("TWFu");
    String text = new String(bytes, StandardCharsets.UTF_8);
    System.out.println(text); // Man
  }
}

Vier Sätze zu dem, was gerade passiert ist. Erstens ist der Einstiegspunkt eine Instanz, nicht die Klasse: decode() lebt auf dem Base64.Decoder-Objekt, das Sie aus der Fabrik bekommen haben. Zweitens, und das ist die eine einzige wichtigste Design-Entscheidung der gesamten API, ist das Ergebnis ein Byte-Array, nie ein String. Der Payload könnte ein Satz, ein JPEG oder ein Hash sein, und keines davon sollte gleich behandelt werden, bevor Sie wissen, was Sie haben, also hält der JDK absichtlich bei den Bytes an. Drittens ist der Sprung von Bytes zu Text ein eigener, bewusster Schritt mit einem expliziten Zeichensatz, und genau in diesem Schritt wird aus "café" Mojibake, wenn Sie nachlässig sind; der Zeichensatz-Abschnitt unten ist ihm gewidmet. Viertens ist die leere Zeichenkette ein Wert erster Klasse: Base64.getDecoder().decode("") gibt Ihnen ein Array der Länge null, keine Ausnahme, kein Aufheben.

Als Testdaten im Kopf: Merken Sie sich, dass TWFu der eigene Smoketest des Standards ist: Wenn Ihr Dekodier-Code es in Man verwandelt, ist die Maschine ehrlich. Der Roundtrip in die andere Richtung ist zwei Zeilen derselben API und bekommt die volle Behandlung im Kodierungs-Leitfaden, der am Ende verlinkt ist.

Das Dekodierer-Aufgebot

Java gibt Ihnen nicht einen Dekodierer, sondern drei, und der Unterschied zwischen ihnen ist eine Policy-Entscheidung darüber, welches Alphabet akzeptiert wird und wie viel Unordnung toleriert wird. Alle drei sind Instanzen derselben verschachtelten Klasse Base64.Decoder. Die Javadoc der Klasse legt die Aufteilung in einem Satz pro Charakter dar. Für die Basis- und URL-sicheren Dekodierer: Der Dekodierer "lehnt Daten ab, die Zeichen außerhalb des Base64-Alphabets enthalten". Für den MIME-Dekodierer: "Alle Zeilentrenner oder andere Zeichen, die in der Base64-Alphabettabelle nicht zu finden sind, werden im Dekodierungsvorgang ignoriert". Dieser zweite Satz ist die gesamte MIME-Geschichte in einer Zeile, und er hat Zähne, denn "ignoriert" bedeutet alles, was kein Alphabet-Zeichen ist, nicht nur Zeilenumbrüche.

Die Wahlregel ist kurz. Standard ist getDecoder(). Wenn der Wert aus einer URL, einem Token oder einer API kam, die "URL-safe" versprach, wechseln Sie zu getUrlDecoder(). Erst wenn Sie tatsächlich MIME-förmige Eingabe erwarten (Zeilenumbrüche alle 76 Zeichen, direkt aus einem Mailsystem), greifen Sie zu getMimeDecoder(). Im Zweifel wählen Sie strikt: Die Aufgabe eines strikten Dekodierers ist es, Überraschungen scheitern zu lassen, und genau das wollen Sie an einer Vertrauensgrenze. Ein nachsichtiger Dekodierer ist dagegen eine Lupe für Beschädigungen: Ein String mit Irrläufer-Zeichen darin wird zu etwas Plausibel-Falschem dekodiert, ohne jeden Fehler.

Die Beschwerden des Dekodierers lesen

Die strikten Dekodierer scheitern laut, und sie scheitern präzise. Jede schlechte Eingabe wirft eine IllegalArgumentException, deren Meldung genau sagt, was schiefgelaufen ist, also ist diese Tabelle das, was Sie lesen, wenn zum ersten Mal ein Produktions-String explodiert. Die Meldungen unten sind die exakte Wortwahl des aktuellen JDK:

Eingabe (zu getDecoder, sofern nicht anders angegeben) Was falsch ist Exakte Meldung
"SGVs bG8s" ein Leerzeichen hat sich geschlichen Illegal base64 character 20
"SGVs\nbG8s" ein Zeilenumbruch hat sich geschlichen Illegal base64 character a
"SGVs$bG8s" ein Dollarzeichen ist nicht im Alphabet Illegal base64 character 24
"SGVsbG8-" ein URL-sicherer Strich im Standard-Dekodierer Illegal base64 character 2d
"ab+c" zu getUrlDecoder() ein Pluszeichen im URL-sicheren Dekodierer Illegal base64 character 2b
"S" ein Symbol kann kein Byte bilden Input byte[] should at least have 2 bytes for base64 bytes
"SG=VsbG8s" Padding mitten in den Daten Input byte array has wrong 4-byte ending unit
"Zm8==" zwei Pads, wo eines hingehört Input byte array has incorrect ending byte at 4
"Z=" ein Zeichen, gefolgt von einem Pad Last unit does not have enough valid bits
"SGVsbG8sIHdvcmxkIQ==xx" Müll nach den Pads Input byte array has incorrect ending byte at 20

Die Hex-Zahl in der Meldung ist der Byte-Wert des Übeltäter-Zeichens, ausgedruckt mit Integer.toString(byte, 16): 20 ist ein Leerzeichen, a ein Line-Feed, d ein Wagenrücklauf, 24 ein Dollarzeichen, 2d der URL-sichere Strich, 2b das Plus, 2f der Schrägstrich, 5f der Unterstrich. Zwei Kuriositäten für die Hinterhand. Erstens kann die Meldung negativ werden: Füttern Sie dem Dekodierer einen String, der é enthält, und er beschwert sich mit Illegal base64 character -17, denn das Zeichen wird zuerst auf das Latin-1-Byte 0xE9 abgebildet, das als signiertes Java-Byte minus 23 ist, und minus 23 im Hexadezimalsystem ist minus 17. Ihr Fehler-Logger verrichtet also für einen Moment Vorzeichen-Arithmetik. Zweitens die Position: In der incorrect ending byte at N-Familie ist N der nullbasierte Index des ersten Bytes, das der Dekodierer nicht in Sinn fassen konnte, was ein Geschenk ist, wenn Sie einen beschädigten Payload durch Halbieren eingrenzen.

Ein Kostümwechsel, den man kennen sollte: Wenn das Dekodieren über den verpackten Stream stattfindet (die wrap(InputStream)-Variante, unten behandelt), treten dieselben Probleme stattdessen als IOException mit einem 0x-Präfix auf: Illegal base64 character 0x20 (aktuelle JDKs; der Stream-Dekodierer von JDK 8 druckt den Lookup-Wert, -1, statt des Bytes). Gleiches Problem, andere Ausnahme, leicht andere Schreibweise. Und der nachsichtige MIME-Dekodierer beschwert sich natürlich über keines davon: Er überspringt einfach. Das ist der Preis des nachsichtigen Charakters.

Die Padding-Regeln

Jeder Base64-String in der wilden Welt macht ein stilles Versprechen über das Padding, und Javas Versprechen ist ungewöhnlich freundlich. Die Javadoc des Dekodierers sagt es genau: Das Padding-Zeichen = "wird akzeptiert und als Ende der kodierten Bytes-Daten interpretiert, ist aber nicht erforderlich". Eine letzte Einheit von zwei oder drei Zeichen wird dekodiert, als wäre sie gepaddet, und wenn Pads vorhanden sind, müssen sie in der exakt richtigen Menge vorhanden sein. Das Verhalten des aktuellen JDK an den klassischen Beispielen:

Eingabe Ergebnis
"" leeres Byte-Array, kein Fehler
"Zm8" "fo", Padding schlicht nicht vorhanden
"Zm8=" "fo", die kanonische Schreibweise
"Zm8==" IllegalArgumentException: incorrect ending byte at 4
"Zm9v=" IllegalArgumentException: wrong 4-byte ending unit
"Zg==" "f", ein Byte
"Z=" IllegalArgumentException: last unit does not have enough valid bits
"AA==" genau ein Byte, das NUL-Byte 0x00
"AAAA" drei NUL-Bytes

Lesen Sie diese Tabelle zweimal. Der leere String dekodiert zu nichts, während AA== zu einem einzelnen NUL-Byte dekodiert: In Base64 sind "nichts" und "eine Null" verschiedene Wesen, und beide sind vollkommen gültige Eingaben. Und Padding muss, wenn es vorhanden ist, exakt sein: Zm8= ist richtig, Zm8== ist falsch, Zm9v= ist falsch, und ein Pad in der Mitte des Strings ist falsch. Praktische Konsequenz für Ihre eigenen Protokolle: Wählen Sie eine Schreibweise (mit oder ohne Padding) und erzwingen Sie sie an beiden Enden, denn ein Wert, der in zwei Schreibweisen ankommen kann, ist ein Wert, der irgendwo weiter hinten eine naive Gleichheitsprüfung brechen kann.

base64url: Das Alphabet, das für URLs gebaut wurde

Standard-Base64 endet im Alphabet mit + und /, und genau das sind die beiden Zeichen, die sich in URLs nicht benehmen: Ein + in einem Query-String ist noch bevor Java es sieht, bereits ein Leerzeichen, ein / ist ein Pfadtrenner, und ein anhängendes = will in ein dreizeichiges Monster prozent-kodiert werden. Abschnitt 5 von RFC 4648 zeichnet die Korrektur: das URL- und Dateinamen-sichere Alphabet, in dem + zu - wird, / zu _ wird und das anhängende =-Padding typischerweise fallen gelassen wird, wenn die Länge implizit bekannt ist. Der RFC ist beim Namen unerbittlich: Diese Kodierung "sollte nicht als dasselbe wie die base64-Kodierung betrachtet werden". Sie begegnen ihr als base64url, und dort leben JSON Web Tokens, OAuth-State-Parameter, API-Sitzungs-IDs und elfzeichenlange Video-IDs alle zusammen.

Der berühmteste base64url-Payload im Web ist der JWT, und ein Blick hinein ist drei Zeilen Arbeit. Token-Teile sind konventionell ungepaddet, und der URL-Dekodierer ist damit zufrieden, denn Padding wird akzeptiert, ist aber nicht erforderlich, erinnern Sie sich:

import java.nio.charset.StandardCharsets;
import java.util.Base64;
public class JwtPeek {
  public static void main(String[] args) {
    String token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9"
      + ".eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ"
      + ".SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c";
    String[] parts = token.split("\\.");
    byte[] header = Base64.getUrlDecoder().decode(parts[0]);
    byte[] payload = Base64.getUrlDecoder().decode(parts[1]);
    System.out.println(new String(header, StandardCharsets.UTF_8));
    // {"alg":"HS256","typ":"JWT"}
    System.out.println(new String(payload, StandardCharsets.UTF_8));
    // {"sub":"1234567890","name":"John Doe","iat":1516239022}
  }
}

Zwei ehrliche Warnungen leben hier. Erstens: Ein JWT zu dekodieren ist ein Blick hinein, kein Vertrauen: Der dritte Teil ist eine Signatur, und die beiden Teile, die Sie gerade gelesen haben, sind weder geheim noch authentifiziert. Einem Payload zu vertrauen, bevor seine Signatur verifiziert ist, ist der klassische JWT-Bug, und die Korrektur besteht darin, die Verifikation einer JOSE-Bibliothek wie JJWT (0.13.0) oder nimbus-jose-jwt (10.9.1) zu übergeben, statt eigenes Krypto zu bauen. Zweitens identifizieren die Fehler die Richtung: Geben Sie einen String mit Standardalphabet an getUrlDecoder() und Sie bekommen Illegal base64 character 2b oder 2f, und umgekehrt verdienen Sie 2d oder 5f. Alphabet-Fehlwahl ist der mit Abstand häufigste Base64-Dekodier-Fehler in der wilden Welt, und die Fehlermeldung zeigt im Handumdrehen darauf. Wenn ein Token in einem Query-String eigentlich Standard-Base64 sein sollte, wurden sein + und / wahrscheinlich vom Transport schon zerknüllt, bevor sie Sie erreichten, und der Dekodier-Fehler erzählt Ihnen von einem Bug stromaufwärts, nicht in Ihrem Dekodierer.

Von Bytes zu Wörtern

Jeder Dekodier-Aufruf in diesem Artikel hält absichtlich bei den Bytes an, denn Base64 ist ein Byte-Format, punkt. Die Frage "Welcher Text war das?" ist Ihre, und die moderne Standardantwort lautet UTF-8. Es gibt aber ein Zeichensatz-Detail auf der Dekoder-Seite der API, das Leute überrascht, also: hier ist es. Der decode(String)-Overload interpretiert Ihren String nicht als UTF-8. Die Javadoc sagt es genau: Ein Aufruf "hat genau dieselbe Wirkung, als würde man decode(src.getBytes(StandardCharsets.ISO_8859_1)) aufrufen". Das ist kein Bug - es ist ein Trick: Das Base64-Alphabet ist reines ASCII, also liefert die Abbildung des Strings durch Latin-1 dem Dekodierer exakt dieselben Bytes zu null Umwandlungskosten, und jedes Nicht-ASCII-Zeichen in der Eingabe wird einfach zu einem ungültigen Symbol, das der strikte Dekodierer ablehnt (dorthin kommen die negativen Hex-Zahlen in den Fehlermeldungen).

Der Zeichensatz des Payload ist eine völlig eigenständige Entscheidung, diejenige am Schritt new String(bytes, charset). Hier ist der klassische Fall: "café" in UTF-8 sind die fünf Bytes 63 61 66 C3 A9, die sich zu Y2Fmw6k= kodieren:

import java.nio.charset.StandardCharsets;
import java.util.Base64;
public class CharsetDecode {
  public static void main(String[] args) {
    byte[] packed = Base64.getDecoder().decode("Y2Fmw6k=");
    System.out.println(new String(packed, StandardCharsets.UTF_8));
    // café, der Akzent überlebt
    System.out.println(new String(packed, StandardCharsets.ISO_8859_1));
    // caf, gefolgt von Mojibake, die UTF-8-Bytes falsch gelesen als Latin-1
  }
}

Die zweite Zeile ist der Fehlermodus, den man sofort erkennen sollte: Ein UTF-8-Payload, gelesen durch Latin-1, der einen String produziert, der genau ein Zeichen zu lang und ein Byte daneben ist. Das Heilmittel ist immer, sich mit dem Erzeuger auf einen Zeichensatz zu einigen und ihn explizit zu übergeben. Und zwar explizit im Code, nicht nur im Kopf: Der new String(bytes)-Konstruktor ohne Argumente verwendet den Standard-Zeichensatz der Plattform, der auf einem Windows-Server Cp1252 sein kann und auf einem älteren Linux alles Mögliche, was die Maschine gerade für gut befindet. Seit JDK 18 (JEP 400, "UTF-8 als Standard") ist der Standard auf jeder Plattform UTF-8, also trifft die argumentfreie Form auf einer modernen JVM zufällig ins Schwarze, aber Ihr Code sollte es trotzdem aussprechen, denn die nächste Person, die ihn liest, sollte nicht wissen müssen, was der Standard ist. Und wenn der Payload gar kein Text ist, bekommt derselbe Code nur ein anderes Ende: Bytes rein, Bytes raus, bis zum allerletzten Schritt.

Wenn der Payload eine Datei ist

Der häufigste Datei-Job ist die Umkehrung irgendeiner Export-Routine: Eine .b64-Textdatei trifft ein, und Sie brauchen die Originaldatei zurück. Mit striktem Dekodieren ist das bereits produktionsreif:

import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.Base64;
public class DecodeFile {
  public static void main(String[] args) throws Exception {
    byte[] packed = Files.readAllBytes(Paths.get("payload.bin.b64"));
    byte[] raw = Base64.getDecoder().decode(packed);
    Files.write(Paths.get("payload.bin"), raw);
  }
}

Nichts auf diesem Weg kümmert sich darum, ob der Payload eine Textdatei, ein ZIP-Archiv oder ein Video ist: byte[] ist einfach nur Bytes. Die Größenrechnung spielt auch in Ihre Richtung: Die dekodierte Ausgabe ist drei Viertel der Länge der kodierten Eingabe, also verschlechtert Dekodieren nie den Speicher, und eine Datei mit mehreren hundert Megabyte in kodierter Form ist die kleinere der beiden. Eine gute Angewohnheit ist es, die Bytes sich ankündigen zu lassen, bevor Sie irgendeinem Etikett vertrauen. Die ersten acht Bytes eines PNG sind immer die magische Zahl 89 50 4E 47 0D 0A 1A 0A, was bedeutet, dass jedes Base64-kodierte PNG, das Sie je treffen werden, mit demselben Präfix beginnt: iVBORw0K: Wenn ein Payload "behauptet", ein Bild zu sein, und nicht so anfängt, stimmt schon irgendwas nicht.

Wenn Sie den Ziel-Buffer schon besitzen, schreibt der Zwei-Array-Overload direkt hinein und gibt genau zurück, wie viele Bytes gelandet sind, ohne jegliche Zwischenallokation:

import java.nio.charset.StandardCharsets;
import java.util.Base64;
public class DecodeInto {
  public static void main(String[] args) {
    byte[] src = "SGVsbG8sIHdvcmxkIQ==".getBytes(StandardCharsets.ISO_8859_1);
    byte[] dst = new byte[16];
    int written = Base64.getDecoder().decode(src, dst);
    System.out.println(written); // 13
    System.out.println(new String(dst, 0, written, StandardCharsets.UTF_8));
    // Hello, world!
  }
}

Eine scharfe Ecke an diesem Overload, in der Javadoc dokumentiert: Wenn das Ziel zu klein ist, werden keine Bytes überhaupt geschrieben, und Sie bekommen IllegalArgumentException: Output byte array is too small for decoding all input bytes. Dimensionieren Sie den Buffer nach der einfachen Rechnung, ungefähr 3 * n / 4 minus Padding, und die Ausnahme zeigt nie ihr Gesicht. Es gibt auch einen ByteBuffer-Overload, der einen frischen Buffer zurückgibt, dessen Limit auf die dekodierte Länge gesetzt ist, praktisch, wenn Ihre Pipeline in NIO lebt.

Vom Kabel: Header, JSON und Data URIs

Base64 trifft Java am häufigsten am Netzwerkrand. Drei Formen verdienen je ein durchgerechnetes Beispiel.

Form eins: der HTTP-Basic-Auth-Header. Der älteste Authentifizierungs-Header des Webs fährt immer noch mit Base64. Gemäß RFC 7617 schickt eine Basic-Anfrage Authorization: Basic gefolgt von der Base64-Kodierung von username:password, und der RFC ist explizit, dass dies Kodierung ist, kein Schutz: Jeder, der die Pakete mitnimmt, kann beide Hälften in einem Tastendruck lesen. Das eigene Beispiel des RFC, QWxhZGRpbjpvcGVuIHNlc2FtZQ==, dekodiert zu Aladdin:open sesame. Das Parsen des Headers auf der Server-Seite ist ein paar Zeilen Arbeit:

import java.nio.charset.StandardCharsets;
import java.util.Base64;
public class BasicAuth {
  public static String[] credentials(String header) {
    if (header == null || !header.startsWith("Basic ")) {
      return null;
    }
    byte[] packed = header.substring(6).getBytes(StandardCharsets.ISO_8859_1);
    byte[] raw = Base64.getDecoder().decode(packed);
    String userPass = new String(raw, StandardCharsets.UTF_8);
    int colon = userPass.indexOf(':');
    if (colon < 0) {
      return null;
    }
    return new String[] {userPass.substring(0, colon), userPass.substring(colon + 1)};
  }
}

Zwei Details halten das sicher. Die Aufteilung am ersten Doppelpunkt zählt, denn ein Passwort darf legal seine eigenen Doppelpunkte enthalten. Und der Vergleich des dekodierten Passworts mit Ihrem gespeicherten Wert sollte in konstanter Zeit laufen: Hashten Sie beide Werte mit SHA-256 und vergleichen Sie die Digests mit MessageDigest.isEqual, nie ein schlichtes equals, mit dem ein Angreifer sich per Timing eine Benutzerliste zusammenbauen könnte. Bieten Sie dies nur über HTTPS an; auf einer unverschlüsselten Verbindung ist die Base64-Schicht nur Fensterschmuck.

Form zwei: Binär in JSON. Ein großer Teil moderner APIs bettet Binäres als Base64-Text in JSON ein: Datei-Upload-Endpunkte, Content-APIs, Secret-Stores und Webhooks machen es alle, denn rohe Bytes würden andernfalls die Escape-Regeln des JSON-Strings brechen. Das Muster ist immer dasselbe: Das Feld trifft als einfacher String ein, und Sie dekodieren es an der Grenze, nicht in Ihren Domänen-Objekten:

import java.util.Base64;
public class ApiField {
  public static void main(String[] args) {
    // Das geparste JSON trug:  "content" : "iVBORw0KGgoAAA..."
    String field = "iVBORw0KGgo=";
    byte[] image = Base64.getUrlDecoder().decode(field);
    // Manche APIs sprechen stattdessen Standard-Base64. Lesen Sie die Spezifikation,
    // und wählen Sie dann entsprechend getDecoder() oder getUrlDecoder().
    System.out.println(image.length); // 8
  }
}

Die Falle hier ist nicht das Dekodieren; sie ist das Lesen der Spezifikation. Manche APIs wollen Standard-Base64 mit Padding, manche base64url ohne, und einige sind bei beiden nachsichtig. Wenn die Spezifikation schweigt, ist die billigste Lösung, einen Beispielwert von der anderen Seite anzuschauen: ein - oder _ irgendwo im Wert klärt das Alphabet, und anhängende = klären das Padding.

Form drei: die Data URI. Jemand klebt ein Bild in ein Formular, und das Frontend reicht Ihnen die komplette Data URI: data:image/png;base64,iVBORw0KGgo.... RFC 2397 definiert die Form: data:, ein optionaler Medientyp, ein optionales ;base64-Flag, ein Komma, und dann die Daten. Wenn das Flag vorhanden ist, ist der Payload Base64; wenn es fehlt, ist der Payload prozent-kodierter Klartext, seltener, aber legal. Wenn der Medientyp weggelassen wird, ist der Standard text/plain;charset=US-ASCII. Eine aufzuteilen ist geradlinig:

import java.util.Base64;
public class DataUri {
  public static void main(String[] args) {
    String uri = "data:image/png;base64,iVBORw0KGgo=";
    int comma = uri.indexOf(',');
    String meta = uri.substring(5, comma);
    String payload = uri.substring(comma + 1);
    boolean isBase64 = meta.endsWith(";base64");
    String mime = isBase64 ? meta.substring(0, meta.length() - 7) : meta;
    byte[] raw = Base64.getDecoder().decode(payload);
    System.out.println(mime + " -> " + raw.length + " bytes");
    // image/png -> 8 bytes
  }
}

Zwei Fallen leben in diesem Format. Das fehlende ;base64-Flag ist die erste: Eine legale Data URI ohne Flag trägt einen prozent-kodierten Payload, und das durch Base64.getDecoder() zu jagen, wirft. Die zweite ist der behauptete Medientyp: Er ist ein Hinweis des Senders, keine Tatsache, also prüfen Sie die magischen Bytes dessen, was Sie dekodiert haben, bevor Sie es unter "png" ablegen. Und denken Sie an den eigenen Rat des RFC, dass Data URIs für kurze Werte sind; ein mehrere-Megabytes-großes Bild in einer URL ist ein schlechter Geruch, kein Muster.

E-Mail, MIME und PEM-Rüstung

Base64 wurde für die Post geboren, und postförmiges Base64 trifft immer noch in Java-Programmen ein. Der MIME-Standard (RFC 2045) machte Base64 zu einer der binären Übertragungskodierungen und fügte zwei Hausregeln hinzu: kodierte Zeilen dürfen 76 Zeichen nicht überschreiten, und Dekodierer müssen jedes Zeichen außerhalb des Alphabets ignorieren, Zeilenumbrüche inklusive. Die strikten Dekodierer lehnen den allerersten Zeilenumbruch ab; getMimeDecoder() wurde genau für diese Eingabe gebaut:

import java.nio.charset.StandardCharsets;
import java.util.Base64;
public class MimeDecode {
  public static void main(String[] args) {
    String wrapped = "SGVs\nbG8s\r\nIHN0\nYW5kYXJk";
    byte[] bytes = Base64.getMimeDecoder().decode(wrapped);
    System.out.println(new String(bytes, StandardCharsets.UTF_8));
    // Hello, standard
  }
}

Das funktioniert, und Sie sollten trotzdem den Haken kennen, denn der Haken hat Zähne. Der nachsichtige Dekodierer ignoriert nicht "Zeilenumbrüche"; er ignoriert alles, was nicht in seinem Alphabet ist. Wenn ein Standard-Base64-String mit Irrläufer-Zeichen beschädigt wird, verschwindet der Müll, und der Rest dekodiert zu etwas Plausibel-Falschem, also greifen Sie zu getMimeDecoder() nur, wenn Sie tatsächlich MIME-förmige Eingabe erwarten.

Der wild lebende Bruder von MIME ist die PEM-Rüstung, das -----BEGIN CERTIFICATE------Geschäft, das Zertifikate und Schlüssel einwickelt. Hier ist die Falle: Die Rüstungs-Zeilen stecken voll mit gewöhnlichen Alphabet-Zeichen. Die Buchstaben in "BEGIN CERTIFICATE" sind einfach Base64-Buchstaben, also dekodiert das Einfüttern eines ganzen PEM-Blocks, Rüstung inklusive, die Rüstung so, als wäre sie Daten. Streichen Sie die Rüstung selbst, und übergeben Sie dann den nackten Körper einem Dekodierer:

import java.util.Base64;
public class PemDecode {
  public static void main(String[] args) {
    String pem = "-----BEGIN CERTIFICATE-----\n"
      + "TUlJQm96Q0NBVWlnQXdJQkFnSUpBSXBhVDJUaVFvZU1BMEdDU3FHU0liM0RRRUE9\n"
      + "-----END CERTIFICATE-----\n";
    String body = pem.replaceAll("(?m)^-----.*$", "").replaceAll("\\s", "");
    byte[] der = Base64.getDecoder().decode(body);
    System.out.println(der.length); // der DER-Körper, ohne Rüstung
  }
}

PEM bricht konventionell bei 64 Zeichen pro Zeile um (MIME bei 76), und sobald der Leerraum verschwunden ist, stimmen der strikte Dekodierer und der MIME-Dekodierer im Ergebnis überein. Verwenden Sie den strikten: Eine Überraschung hat wenigstens den Anstand, zu werfen. Für den schmutzigen, aber Standard-Fall ist das klassische Rezept, den bekannten Leerraum zu streichen und mit der strikten Instanz zu dekodieren, und jeden verbliebenen Müll dazu zu bringen, eine IllegalArgumentException zu verdienen, statt ein beschädigtes Zertifikat.

Config, Umgebungs- und Datenbank-Werte

Base64 ist ein Text-Container, deshalb taucht er an Orten auf, an die man nicht denkt. In Datenbanken kann ein binärer Blob (eine Datei, ein Icon, eine serialisierte Struktur) in einer TEXT-Spalte als Base64 leben und überleben, was auch immer an Werkzeugen Text voraussetzt; erwarten Sie, dass der gespeicherte Wert etwa ein Drittel größer ist als das Original, und dimensionieren Sie die Spalte entsprechend. In Konfigurationsdateien und Umgebungsvariablen ist Base64 der Trick, Werte unterzubringen, die andernfalls das Format brechen würden: eine DSN mit Semikolons, ein Passwort mit Anführungszeichen, ein mehrzeiliges Zertifikat. Dekodieren beim Start ist der ganze Job:

import java.nio.charset.StandardCharsets;
import java.util.Base64;
public class ConfigDecode {
  public static void main(String[] args) {
    String value = System.getenv("DB_DSN_B64");
    if (value == null) {
      return;
    }
    byte[] raw = Base64.getDecoder().decode(value);
    String dsn = new String(raw, StandardCharsets.UTF_8);
    // dsn könnte sein: pg:host=db;password=qu"ote
  }
}

Die gleiche Vorsicht gilt hier doppelt. Erstens ist das Formatsicherheit, keine Geheimhaltung: In dem Moment, in dem ein Entwickler die Konfigurationsdatei liest, kann er den Wert in einem Aufruf dekodieren, also speichern Sie nie ein Geheimnis als Base64 und nennen Sie es verschlüsselt; der Sicherheitsabschnitt unten geht ins Detail. Zweitens, validieren Sie beim Start: Ein beschädigter oder halbbeklebter Umgebungs-Wert ist eine IllegalArgumentException vom strikten Aufruf, und eine zwei-zeilige Prüfung verwandelt einen kryptischen Laufzeit-Fehler in eine handelbare Startmeldung. Eine Java-spezifische Notiz für die Datenbank-Menge: Halten Sie das dekodierte Binäre als byte[] (einen byte[]-Parameter in Ihrem JDBC-Code), und führen Sie Binäres nie durch einen String im Kreis zurück, denn die String-Konstruktoren sind der Ort, an dem binäre Payloads sterben gehen.

Streaming der Großen Sachen

Für Payloads, die groß sind, aber immer noch in einen Buffer passen, den Sie verwalten, sind die Array-APIs gut. Für Payloads, die gar nicht in den Speicher gehören, ist der Stream-Adapter der Zug: wrap(InputStream) gibt einen Eingabestream zurück, der dekodiert, während Sie lesen, sodass eine kodierte Datei mit mehreren Gigabyte nie in einem Byte-Array sitzen muss:

import java.io.InputStream;
import java.io.OutputStream;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.Base64;
public class StreamDecode {
  public static void main(String[] args) throws Exception {
    InputStream packed = Base64.getDecoder().wrap(Files.newInputStream(Paths.get("bigfile.b64")));
    OutputStream raw = Files.newOutputStream(Paths.get("bigfile.bin"));
    byte[] buf = new byte[8192];
    int n;
    while ((n = packed.read(buf)) != -1) {
      raw.write(buf, 0, n);
    }
    raw.close();
    packed.close();
  }
}

Zwei Details sind zu kennen. Die Read-Methoden des verpackten Streams werfen eine IOException, wenn sie auf Bytes stoßen, die nicht dekodiert werden können, also scheitert eine beschädigte Datei mit einer Stream-Ausnahme statt einer IllegalArgumentException. Und das Schließen des verpackten Streams schließt den zugrunde liegenden Stream, also schließt das Beispiel packed zuletzt, nach der Kopierschleife, und in Produktion würden Sie beide in einen try-with-resources-Block legen. (Der 8192-Buffer ist einfach ein geräumiger Lese-Buffer; der verpackte Stream dekodiert intern, also ist die Größe, mit der Sie lesen, eine Performance-Entscheidung, keine Protokoll-Vorgabe.)

Jetzt eine Legacy-Warnung, denn das ist der eine echte Bug in der ganzen Geschichte, und er hat eine Bug-Nummer. Auf jedem JDK vor 16 (der Bug-Report reproduziert ihn auf 8, 10 und 11) fügt das Lesen eines verpackten Dekodierers mit bestimmten Buffer-Größen zwei Irrläufer-Null-Bytes ans Ende der dekodierten Daten hinzu: JDK 8222187, dessen klassische Reproduktion einen sieben-Byte-Lese-Buffer mit einer schlichten acht-Byte-Eingabe koppelt, und er ist in JDK 16 behoben. Wenn Sie auf einem Legacy-JDK 8 streamen müssen, prüfen Sie die dekodierte Länge nach dem Kopieren nach, denn der Bug feuert für bestimmte Eingabe-und-Buffer-Kombinationen, und sogar ein 4096-Byte-Buffer wurde in der wilden Welt gemeldet, oder besser, upgraden Sie das JDK, das ohnehin noch etwa einhundert andere Dinge beheben würde.

Paketband, kein Schloss

Jetzt der Abschnitt, der die Vorsichtigen von den Verbrannten trennt. Base64 ist keine Verschlüsselung, und der Standard selbst sagt es doppelt. RFC 4648, Abschnitt 12: Base-Kodierung "versteckt visuell sonst leicht erkennbare Informationen wie Passwörter, bietet aber keine rechnerische Vertraulichkeit", und er fährt fort zu bemerken, dass dies "bekanntlich Sicherheitsvorfälle verursacht hat", wenn jemand einen Protokoll-Austausch in ein Ticket klebt und versehentlich das Passwort verrät. Auch der Rat des RFC für Implementierer verdient einen Rahmen: "Ein Dekodierer sollte bei ungültiger Eingabe nicht brechen, einschließlich beispielsweise eingebetteter NUL-Zeichen".

Die feinere Falle ist die Verformbarkeit. Denken Sie daran, dass jedes Symbol sechs Bits trägt, und dass eine kurze letzte Einheit Reserve-Bits übrig lässt, die in einer wohlgeformten Kodierung null sein müssen. Ein nachlässiger oder feindlicher Kodierer kann Müll in diese Reserve-Bits legen, und das Ergebnis sieht trotzdem vollkommen gültig aus: MQ== und MT== dekodieren beide zu dem einzelnen Byte der Ziffer 1. Java schlug hier die verzeihende Seite ein: Base64.getDecoder().decode("MT==") prüft die nicht-signifikanten Bits nicht und gibt Ihnen fröhlich dasselbe Byte. Warum sollte man sich kümmern? Weil zwei verschiedene Strings, die zu denselben Daten dekodieren, die "einzigartige Schreibweise"-Annahme brechen, auf die Hash-Prüfungen, Duplikatbeseitigung und Signaturvergleiche still vertrauen, und ein Angreifer, der einen kodierten Wert unterwegs manipulieren kann, eine Schreibweise durch die andere tauschen kann. Das Paper von 2022 "Base64-Malleability in der Praxis" von Chatzigiannis und Chalkias (ACM ASIA CCS 2022) geht genau diesen Inkonsistenzen über reale Implementierungen hinweg nach. Die eigenen Worte des RFC über die Reserve-Bits: Sie "können missbraucht werden, um Informationen zu leaken, oder verwendet werden, um String-Gleichheitsvergleiche zu umgehen oder Implementierungsprobleme auszulösen". Die praktische Regel ist nicht "nie dekodieren"; sie ist "kenne deine Grenze": Für Daten zwischen Ihren eigenen Systemen ist die Großzügigkeit des JDK in Ordnung, aber für Daten, die eine Vertrauensgrenze überqueren, erzwingen Sie die kanonische Form (korrekte Länge, null Reserve-Bits, eine Schreibweise des Paddings), bevor Sie dem vertrauen, was Sie dekodiert haben.

Performance-Notizen

Hier ist die gute Nachricht in einem Satz: Auf einer modernen JVM ist der eingebaute Dekodierer schnell genug, dass Base64 fast nie Ihr Flaschenhals ist, und er ist der Referenzwert, gegen den sich der Rest des Ökosystems Benchmarkt. Ein Fall, der das beweist: 2025 hat das gRPC-java-Projekt öffentlich seinen auf Guava basierenden Base64-Handler gegen java.util.Base64 gemessen (Issue 11857), und die JDK-Implementierung kam auf JDK 17 und 21 mit etwa 2,5- bis 3,8-mal schnellerem Kodieren und 1,3- bis 2,1-mal schnellerem Dekodieren heraus, mit den größten Abständen auf x86. Das ist ein starker Hinweis darauf, wo der Implementierungs-Aufwand des JDK hingeht, und es ist derselbe Schluss, den Sie immer wieder in Base64-Benchmarks finden: Die Standardbibliotheks-Version ist jetzt die schnelle, nicht die Legacy-Version.

Zwei praktische Notizen. Erstens: Bei riesigen Dateien ist das Speicherprofil, nicht die Geschwindigkeit, das, was Sie verwalten, und deshalb gibt es den Streaming-Abschnitt: wrap(InputStream) hält den Arbeitsbereich auf Ihrem Lese-Buffer. Zweitens: Wenn Sie tatsächlich auf einem heißen Pfad landen, der Millionen kleiner Werte dekodiert, teilen Sie eine Dekodierer-Instanz (die Fabrik gibt bereits dieselbe geteilte zurück, wie oben angemerkt), überspringen Sie den decode(String)-Overload, wenn Sie schon Bytes haben (er kopiert den String zuerst durch Latin-1), und lassen Sie den decode(byte[], byte[])-Overload in ein vorgroßgezogenes Ziel-Array schreiben, um den Allokationstanz zu überspringen.

Fallen mit Java-Akzent

Die Fallen, an einem Ort gesammelt, alle Java-spezifisch:

  • Falscher Dekodierer für das Alphabet. Ein base64url-String in getDecoder() (oder umgekehrt) ist der klassische Illegal base64 character-Crash, meistens mit einem 2d, 5f, 2b oder 2f in der Meldung. Passen Sie den Dekodierer jedes Mal an das Protokoll an.
  • Anhängender Leerraum aus der wilden Welt. Werte, die aus einem Terminal, einer Umgebungsvariablen oder einer Konfigurationsdatei kopiert wurden, treffen oft mit einem Zeilenumbruch ein, und der strikte Dekodierer macht daraus Illegal base64 character a. strip()en Sie die Eingabe, oder verwenden Sie den MIME-Dekodierer nur, wenn die Daten tatsächlich umgebrochen sind.
  • Die Rüstungs-Falle. getMimeDecoder() versteht keine PEM-Header, und die Buchstaben in BEGIN und CERTIFICATE werden als Daten dekodiert. Streichen Sie die Rüstungs-Zeilen selbst, immer.
  • MIME-Nachsicht als Kurzweg. Dekodieren mit dem MIME-Dekodierer nur, um "auf der sicheren Seite" zu sein, überspringt jedes Nicht-Alphabet-Zeichen still, sodass ein beschädigter Payload plausibel und falsch herauskommen kann. Verwenden Sie ihn nur für echte MIME-Eingabe.
  • Zeichensatz dem Glück überlassen. Der argumentfreie new String(bytes) verwendet den Plattform-Standard. Auf JDK 18+ ist das UTF-8, aber Ihr Code sollte StandardCharsets.UTF_8 explizit übergeben, oder genießen Sie Mojibake nach der nächsten Server-Migration.
  • Binäres in einen String verpacken. new String(decodedPng) und zurück ist Zerstörung von Daten: Jede Byte-Folge, die in Ihrem Zeichensatz ungültig ist, wird zum Ersetzungszeichen, und die Rückreise ist ein Weg ohne Rückkehr. Bytes rein, Bytes raus, bis zum allerletzten Schritt.
  • Vertrauen in die Reserve-Bits. MT== dekodiert genauso wie MQ==, also besteht ein Payload mit Müll, der in den nicht-signifikanten Bits versteckt ist, jede Prüfung, die der JDK durchführt. Wenn das Protokoll zählt, erzwingen Sie die kanonische Form.
  • JDK-8-, 11- und 12-Streams. Der verpackte Dekodierer auf diesen Versionen kann bei bestimmten Buffer-Größen zwei Irrläufer-Null-Bytes anhängen (JDK 8222187, in 16 behoben). Auf 16 und später ist das kein Problem; auf den älteren schon.
  • null ist nicht leer. null an decode() zu übergeben ist eine NullPointerException, nicht ein leeres Array. Wenn eine Variable null sein kann, falten Sie sie vor dem Aufruf zusammen.
  • Android ist ein anderer Zoo. Auf Android existiert java.util.Base64 erst ab API-Stufe 26; darunter ist die Framework-Klasse android.util.Base64 mit ihren eigenen Flag-Konstanten. Code, der das eine oder das andere ohne Prüfung fest codiert, bricht genau auf den Geräten, die Sie nie getestet haben.
  • Vergessen Sie: Es ist keine Sicherheit. Base64 versteckt ein Passwort vor einem Blick und vor niemandem sonst. Wenn die Daten ein Geheimnis sind, verschlüsseln Sie sie zuerst, und packen Sie sie nur dann ein, wenn der Kanal Text verlangt.

Der lange Weg zu java.util.Base64

Die Format-Geschichte ist älter als Java. In den 1980ern konnte die Mail-Infrastruktur des Internets nur 7-Bit-ASCII tragen, und Leute, die Binäres bewegen wollten, erfanden lokale Dialekte: uuencode für UNIX (sein Alphabet läuft durch aufeinanderfolgende ASCII-Codes, also war Kodieren eine einzige Addition von 32 ohne Nachschlagetabelle) und BinHex für Apple-Maschinen (das sein Alphabet kuratierte, um visuell verwechselbare Zeichen wie 7, O, g und o zu streichen). 1987 standardisierte das Privacy-Enhanced-Mail-Protokoll (RFC 989) das 64-Zeichen-Schema mit 64-Zeichen-Zeilen zum Tragen von Zertifikaten, und RFC 1421 im Jahr 1993 behielt das Alphabet und die Padding-Regeln. 1996 trug MIME (RFC 2045, die Aktualisierung von RFC 1521 aus 1993) das Schema, das nach seinem 64-Zeichen-Alphabet bereits "base64" hieß, setzte die 76-Zeichen-Zeilenlänge, die Ihre E-Mail-Anhänge noch immer umbricht, und schrieb die nachsichtige Dekodierer-Regel, die getMimeDecoder() bis heute implementiert. 2003 versuchte RFC 3548, die ganze Familie aufzuräumen, und erklärte, dass Dekodierer Zeichen außerhalb des Alphabets ablehnen sollten, und 2006 wurde RFC 4648 der Standard, den alle zitieren, mit den Alphabettabeln, der base64url-Variante in Abschnitt 5 und dem Sicherheitsabschnitt, der den letzten Abschnitt dieses Artikels ehrlich hält.

Javas eigenes Kapitel ist ein bisschen dramatischer. Jahre lang war das einzige Base64 im JDK das interne Paar sun.misc.BASE64Encoder und sun.misc.BASE64Decoder, die Art von API, die heute kompiliert und ohne Deprecation-Warnung verschwindet, und wenn man in einer XML-Welt Base64 brauchte, gab es da auch noch javax.xml.bind.DatatypeConverter von JAXB. Alle anderen nutzten Apache Commons Codec oder Guava. Dann der 18. März 2014: Java 8 brachte java.util.Base64, das RFC 4648 und RFC 2045 in einer Klasse mit dem Factory-Methoden-Muster implementierte, das Sie die ganze Zeit verwendet haben. Dreieinhalb Jahre später entfernte Java 9 (21. September 2017) das sun.misc-Paar für immer, und der offizielle Migrationsleitfaden ist dabei ungeschminkt: "Besonders hervorzuheben: sun.misc.BASE64Encoder und sun.misc.BASE64Decoder wurden entfernt. Stattdessen die unterstützte java.util.Base64-Klasse verwenden, die in JDK 8 hinzugefügt wurde". Führen Sie jdeps auf alten Code aus, der sie noch referenziert, und das Tool markiert die Abhängigkeit als "JDK removed internal API". Java 11 folgte, indem es das JAXB-Modul samt dessen DatatypeConverter entfernte (JEP 320). Seit 1.8 hat sich die öffentliche API um keine einzige Methode geändert, und die Javadoc trägt immer noch ihr ursprüngliches Since: 1.8-Tag. Bewegt hat sich der Motor darunter: Bug-Fixes (der Stream-Bug JDK 8222187, in JDK 16 behoben) und Performance-Arbeit, und deshalb landen Community-Benchmarks immer wieder auf demselben Schluss. Zwölf Jahre, eine API, und sie ist immer noch das schnellste Base64, für das Sie nicht zahlen müssen.

Spielerische Fakten, Java-Edition

Weil ein vollständiger Leitfaden mit einem Lächeln enden sollte, hier ein paar Java-spezifische Fakten, die einfach Spaß machen:

  • Die Oracle-Javadoc für decode(byte[] src, byte[] dst) verspricht, dass "einige Bytes möglicherweise bereits in das Ausgabe-Byte-Array geschrieben wurden, bevor IllegalargumentException geworfen wird". Nicht IllegalArgumentException, IllegalargumentException, mit einem kleinen a. Der Tippfehler ist im echten JDK-Quellcode, und er ist seit 2014 dort. Dokumentation, die sich so sehr auf einen Tippfehler festgelegt hat, ist seltener, als sie sein sollte.
  • Dekodieren Sie einen String, der é enthält, und die Fehlermeldung lautet Illegal base64 character -17: eine negative Hex-Zahl, denn das Zeichen wird zum Latin-1-Byte 0xE9, das als signiertes Java-Byte minus 23 ist, und der JDK druckt es in Basis 16. Ihr Fehler-Logger verrichtet also für einen Moment Vorzeichen-Arithmetik.
  • Base64.getDecoder() == Base64.getDecoder() ist wahr. Der Quellcode gibt bei jedem Aufruf eine geteilte statische Instanz zurück, also ist die "holen Sie sich eine neue"-API ein Kostüm für einen Singleton, und das Versprechen der Thread-Sicherheit ist nur eine Beschreibung dessen, was die JVM ohnehin schon tut.
  • Füttern Sie dem URL-Dekodierer einen String aus vier Unterstrichen, "____", und er gibt drei Bytes reines 0xFF zurück. Der Unterstrich ist der Alphabet-Wert 63, vier davon machen 24 Bits, und 24 Bits aus Einsen sind das Byte-Trio FF FF FF. Nichts Illegales daran, und das ist der lustigste Teil.
  • AA== dekodiert zu einem einzelnen NUL-Byte, während der leere String zu nichts dekodiert. In Base64 sind "nichts" und "eine Null" verschiedene Wesen, und beide sind vollkommen gültige Eingaben.
  • Der kleine String TWFu, der zu Man dekodiert, ist zum beliebtesten Smoketest des Ökosystems geworden: Er taucht im RFC auf, in Wikipedia, in Referenz-Handbüchern und in den meisten Base64-Tutorials auf Erden, also hat jeder seitdem geschriebene Dekodierer dieselbe kleine Hommage gezahlt.
  • Jedes Base64-kodierte PNG, das Sie je dekodiert haben, beginnt mit iVBORw0K. Das ist die PNG-Magie-Zahl im Verkleidungskostüm, und es ist eines der erkennbarsten Acht-Zeichen-Präfixe im Internet.
  • Der URL-sichere Abschnitt von RFC 4648 ist der Ort, an dem der Name "base64url" geboren wird: Die Spezifikation sagt, die Kodierung "kann als base64url bezeichnet werden", und warnt, sie "sollte nicht als dasselbe wie die base64-Kodierung betrachtet werden". Der Ursprung des URL-sicheren Alphabets ist mit einer Fußnote auf einen 2001er-Post in einer P2P-hackers-Mailingliste verwiesen, also hat der Name, den Sie in jede URL kleben, eine Mailingliste-Herkunft.
  • YouTube-Video-IDs sind base64url ohne Padding, der vertraute Elf-Zeichen-String, den man überall in einer URL einfügen kann. Das Format, das für E-Mail-Anhänge entworfen wurde, betreibt jetzt eine Videoplattform, und getUrlDecoder() ist der Teil Ihres JDKs, der das möglich macht.
  • Dekodieren Sie den String YmFzZTY0, und Sie bekommen das Wort base64 zurück, kein Padding nötig, denn sechs ist ein Vielfaches von drei. Ein Format, das sich selbst beschreibt, ist das technische Äquivalent eines Spiegels, der in Morse spricht.

Die andere Richtung

Das war die Dekoder-Seite der Geschichte, und dort wohnt der größte Teil des Schmerzes, denn beim Dekodieren treffen Sie auf die Daten anderer Leute: ihre Padding-Wahlen, ihre Zeilenumbrüche, ihre Zeichensätze, ihre Tokens, ihre Rüstung. Die andere Richtung, Bytes mit den Kodierern von java.util.Base64 in einen Base64-String zu verwandeln, ist ein ruhigeres Tier: Sie wirft nie bei ungültiger Eingabe (es gibt keine ungültige Eingabe zum Kodieren), sie hat eine Größen-Rechnung zu bezahlen statt einer Fehlermeldung zu lesen, und ihre eigene Sammlung von Fallen (der Zeichensatz-Schritt, die MIME-Regler, die Padding-Entscheidung für Tokens) bekommt einen eigenen Leitfaden. Base64-Kodierung in Java, von dieser Seite verlinkt, behandelt den Kodierer mit derselben Tiefe, und die beiden lesen sich bequem als Paar.

Zuletzt aktualisiert: 2026-09-08

Verwandter Artikel: Base64-Kodierung in Java: Ein vollständiger Leitfaden