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-Kodierung in Java: Ein vollständiger Leitfaden

Hier ist die Lage: Sie haben Bytes. Eine Datei, ein Passwort, ein Zertifikat, eine 13-Byte-Begrüßung, ein 200-Megabyte-Upload. Und Sie brauchen sie in etwas, das nur Text versteht: ein JSON-Feld, ein HTTP-Header, eine Datenbank-Spalte, eine URL, eine Konfigurationsdatei. Das ist der gesamte Job von Base64, und dieser Leitfaden ist das Java-Handbuch, es gut zu machen. Kurze Orientierung, denn die Startseite geht das Format Schritt für Schritt durch: Base64 schreibt jeweils drei Bytes der Daten als vier Zeichen aus einem Alphabet von 64 Buchstaben um, und hängt ein oder zwei =-Pads dran, wenn das letzte Stück kurz ist. Der Preis der Fahrt ist die Größe: Aus jeweils drei Bytes werden vier Zeichen, also landet die kodierte Ausgabe etwa 33 Prozent größer als die Eingabe, plus ein kleines bisschen mehr, wenn Zeilenumbrüche beteiligt sind.

Die Schlagzeile, und es ist eine gute. Seit dem 18. März 2014 liefert jedes JDK ein komplettes Base64-Werkzeugset in der Standardbibliothek mit: java.util.Base64. Kein Download, keine Maven-Koordinate, keine native Bibliothek. Ein Import, drei Encoder-Persönlichkeiten, und dasselbe Verhalten von Java 8 bis zum heutigen Java 26. Alles in diesem Artikel baut auf diese eine Klasse auf, und sie wirft nie an den Daten selbst: Der Job des Encoders kann an ungültiger Eingabe nicht scheitern, denn jedes mögliche Byte ist kodierbar.

Eine ehrliche Grenze, bevor wir starten: Dies ist die Kodierer-Seite der Geschichte. Sie lernen die String-nach-Bytes-Entscheidung, die tatsächlich die Korrektheit bestimmt, die Padding- und Umbruch-Regler, base64url und seinen No-Padding-Modus für Tokens, und die Anwendungsfälle, in denen Java-Entwickler am häufigsten auf kodierte Ausgabe treffen. Dekodieren, wo der größte Teil des echten Schmerzes wohnt, bekommt seinen eigenen Leitfaden und ist am Ende dieses hier verlinkt.

Ein Import, null Downloads

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 zwölf Jahre später 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 einer Debian-basierten Box ist das ein einzelner Befehl:

sudo apt install openjdk-17-jdk-headless

Die API ist eine Fabrik: Sie konstruieren nie einen Encoder; Sie bitten die Klasse um einen. Die Kodierer-Seite hat vier Türen, und alle geben Instanzen der verschachtelten Klasse Base64.Encoder zurück:

Fabrikmethode Alphabet Ausgabeform
getEncoder() A-Z a-z 0-9 + / Gepaddet, keine Zeilenumbrüche
getUrlEncoder() A-Z a-z 0-9 - _ Gepaddet, keine Zeilenumbrüche
getMimeEncoder() A-Z a-z 0-9 + / Gepaddet, 76-Zeichen-Zeilen, CRLF
getMimeEncoder(int, byte[]) A-Z a-z 0-9 + / Gepaddet, Ihre Zeilenlänge, Ihr Trenner

Drei Eigenschaften lohnen es, sie vorher zu kennen. Die Instanzen sind threadsicher, und die Fabrik gibt bei jedem Aufruf dieselbe gemeinsame Instanz zurück, also ist Base64.getEncoder() == Base64.getEncoder() wahr; erstellen Sie eine in einem statischen Feld und teilen Sie sie überall. Die Encoder werfen nie aufgrund der Daten: Jeder Byte-Wert hat eine Kodierung, es gibt also keinen "ungültige Eingabe"-Zustand zu behandeln, und die einzigen Exceptions, die Sie treffen, drehen sich um Fehlkonfiguration (ein schlechter Zeilentrenner) oder ein zu kleines Ziel-Array. Und jeder Encoder in dieser Liste fügt standardmäßig Padding hinzu; der Regler, der es abschaltet, withoutPadding(), taucht in der base64url-Sektion auf, denn dort werden Sie ihn brauchen.

Sie werden in Codebasen trotzdem ältere Bibliotheken treffen, also hier eine schnelle Landkarte des Geländes. Apache Commons Codec (derzeit 1.22.1) liefert seit 1.0 sein eigenes org.apache.commons.codec.binary.Base64 mit, mit einer Builder-API, die die strenge oder tolerante Ausrichtung, die Zeilenlänge und den Trenner als Regler offenlegt; ist es das richtige Werkzeug nur, wenn Sie JVMs von vor Java 8 unterstützen müssen. Guava liefert com.google.common.io.BaseEncoding, einen ebenso fähigen Veteranen, der in Big-Data-Stacks immer noch verbreitet ist. Für alles auf einer modernen JVM ist java.util.Base64 der Standard: null Abhängigkeiten, und Community-Benchmarks halten es immer wieder für das schnellste aller Kandidaten (mehr dazu in der Sicherheits-und-Geschwindigkeits-Sektion).

Den ersten String kodieren

Neunzig Prozent des Kodier-Alltags passen in drei Zeilen. Hier ist die ganze Zeremonie, mit dem kleinsten Beispiel, das der Base64-Artikel der Wikipedia verwendet, um das Alphabet zu erklären:

import java.nio.charset.StandardCharsets;
import java.util.Base64;
public class FirstEncode {
  public static void main(String[] args) {
    byte[] text = "Man".getBytes(StandardCharsets.UTF_8);
    String packed = Base64.getEncoder().encodeToString(text);
    System.out.println(packed); // TWFu
  }
}

Der String TWFu ist das Beispiel, das der Base64-Artikel der Wikipedia verwendet, um das Alphabet zu erklären, und wenn Ihr Encoder "Man" in diesen String verwandelt, ist die Maschine ehrlich. Aber schauen Sie sich die erste Zeile von diesem Beispiel an, denn das ist die Zeile, in der in Java tatsächlich kodiert wird. Bewusst gibt es keine encodeToString(String)-Methode. Ein Java-String ist eine Folge von UTF-16-Codeeinheiten, keine Bytes, und Base64 ist ein Byte-Format, also lässt die API die Byte-Frage von Ihnen selbst entscheiden: "Man".getBytes(StandardCharsets.UTF_8). Genau dieser eine Aufruf, mit explizitem Zeichensatz, ist der Ort, an dem "café" die nächsten hundert Jahre korrekt bleibt, und er ist die wichtigste Gewohnheit in diesem ganzen Artikel. Die nächste Sektion ist ihm gewidmet, denn die Alternative ist der klassische Mojibake-Bug.

Zwei Notizen zur zweiten Zeile. encodeToString() gibt einen String zurück, der aus den kodierten Bytes aufgebaut ist; die Javadoc erklärt, dass er das Ergebnis mit dem ISO-8859-1-Zeichensatz konstruiert, was in der Praxis kein Thema ist, denn jedes Base64-Ausgabezeichen ist schlichtes ASCII und sieht in Latin-1, UTF-8 und dem meiste Rest des Zeichensatz-Zoos identisch aus. Und wenn Sie den Ausgabe-Puffer lieber selbst besitzen wollen, gibt encode(byte[]) ein frisches byte[] zurück, und encode(byte[] src, byte[] dst) schreibt in ein Ziel, das Sie liefern, und gibt die Anzahl zurück (und wirft IllegalArgumentException: Output byte array is too small for encoding all input bytes, wenn das Ziel zu klein ist, ohne ein einziges Byte zu schreiben).

Die Zeichensatz-Entscheidung

Machen wir den String-nach-Bytes-Schritt mit dem klassischen Fall konkret. Das Wort "café" ist ein Wort, aber in Bytes hängt es komplett vom Zeichensatz ab, den Sie gewählt haben:

import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.util.Base64;
public class CharsetEncode {
  public static void main(String[] args) {
    byte[] utf8 = "café".getBytes(StandardCharsets.UTF_8);
    byte[] latin1 = "café".getBytes(Charset.forName("ISO-8859-1"));
    System.out.println(utf8.length + " vs " + latin1.length);
    // 5 vs 4: der Akzent ist zwei Bytes in UTF-8, eines in Latin-1
    System.out.println(Base64.getEncoder().encodeToString(utf8));
    // Y2Fmw6k=
    System.out.println(Base64.getEncoder().encodeToString(latin1));
    // Y2Fm6Q==
  }
}

Zwei verschiedene Base64-Strings für ein Wort, und beide sind "korrekt", solange der Leser weiß, welchen Zeichensatz er verwenden soll. Die ganze Lektion passt in eine Zeile: Der Encoder ist den Bytes, die Sie ihm geben, treu, und Sie sind für die Bytes verantwortlich. In der Praxis heißt das: Einigen Sie sich mit Ihrem Gegenüber auf UTF-8, übergeben Sie StandardCharsets.UTF_8 explizit, und schreiben Sie den Zeichensatz in die Spezifikation, das Schema oder die Commit-Meldung, denn niemand auf der Empfänger-Seite kann ihn aus dem Base64 allein raten. Der Dekoder-seitige Zwilling dieses Bugs ist das Thema des Schwester-Leitfadens.

Eine Versionsnotiz, denn sie ändert den Fehlermodus von faulem Code. Das new String(bytes) ohne Argumente und das String.getBytes() ohne Zeichensatz verwenden den Standard-Zeichensatz der Plattform, der historisch auf Windows Cp1252 war und auf Linux etwas Locale-Abhängiges. Seit JDK 18 (JEP 400, "UTF-8 by Default") ist der Standard auf jeder Plattform UTF-8, also ist die faule Form auf einer modernen JVM zufällig richtig. Das macht sie nicht sicher: Ihr Code wird das JDK überleben, für das er geschrieben wurde, und derjenige, der ihn erbt, sollte nicht wissen müssen, was der Standard ist. Schreiben Sie den Zeichensatz.

Ein verwandtes Design-Detail: Irgendwo in der API gibt es kein encode(String)-Overload, und das ist bewusst. Jeder andere Schritt der Pipeline (Arrays, Puffer, Streams) nimmt Bytes, und eine Methode, die Strings annimmt, müsste für Sie einen Zeichensatz wählen, was genau die Entscheidung ist, die das JDK verweigert. Die eine String-typisierte Methode, die existiert, encodeToString, liegt auf der Ausgabe-Seite, wo die Zeichensatz-Frage nicht existiert: Base64-Ausgabe ist reines ASCII. Die gesamte Form der API ist ein kleines Argument für "Entscheiden Sie Ihre Bytes mit Absicht".

Padding, Umbruch und der MIME-Regler

Javas Encoder treffen standardmäßig zwei Formatierungs-Entscheidungen für Sie, und beide lohnen es zu verstehen, denn beide sind Regler, die Sie drehen können. Die erste ist das Padding: Jeder Encoder fügt die =-Zeichen hinzu, die die Ausgabe zu einem Vielfachen von vier machen, wie RFC 4648 es verlangt: Implementierungen müssen geeignete Füllzeichen am Ende der kodierten Daten einfügen, sofern die referenzierende Spezifikation nichts anderes sagt. Die zweite ist der Zeilen-Umbruch: Nur der MIME-Encoder bricht um, bei 76 Zeichen mit Wagenreturn und Zeilenvorschub, und er fügt nach der letzten unvollständigen Zeile keinen Zeilentrenner hinzu, ein Detail, das die Javadoc explizit heraushebt und andere Tools falsch machen:

Encoder Paddet die Ausgabe Bricht Zeilen um Zeilentrenner
getEncoder() ja nein k.A.
getUrlEncoder() ja nein k.A.
getMimeEncoder() ja ja, 76 Zeichen CRLF
getMimeEncoder(64, "\n") ja ja, 64 Zeichen LF

Der MIME-Regler ist der nützlichste Teil der API für Leute, die Formate anderer Leute erben. Der Standard-Konstruktor ist getMimeEncoder() (76, CRLF, direkt aus RFC 2045); die Zwei-Argument-Version, getMimeEncoder(int lineLength, byte[] lineSeparator), lässt Sie andere Konventionen nachbilden. Zwei Kuriositäten, die Sie kennen sollten: Die Zeilenlänge wird "auf das nächste niedrigere Vielfache von 4 abgerundet", also liefert eine Anfrage nach 77 still und leise 76, und ein gerundeter Wert, der nicht positiv ist, liefert gar keinen Umbruch; und der Trenner darf kein Zeichen des Base64-Alphabets enthalten, sonst wirft der Konstruktor an Ort und Stelle eine IllegalArgumentException, denn ein Trenner, der mit Daten verwechselt werden kann, ist ein Bug, der darauf wartet, passiert zu werden. Hier ist der Regler in Aktion, MIME-Standard und PEM-Flavour:

import java.nio.charset.StandardCharsets;
import java.util.Base64;
public class WrapDials {
  public static void main(String[] args) {
    byte[] data = "Hello, wrapped world! This line keeps going and going and going until it finally has to wrap.".getBytes(StandardCharsets.UTF_8);
    Base64.Encoder mime = Base64.getMimeEncoder();
    Base64.Encoder pem = Base64.getMimeEncoder(64, "\n".getBytes(StandardCharsets.ISO_8859_1));
    System.out.println(mime.encodeToString(data));
    // 76-Zeichen-Zeilen, CRLF dazwischen
    System.out.println(pem.encodeToString(data));
    // 64-Zeichen-Zeilen, nacktes LF dazwischen
  }
}

Zwei praktische Notizen. Wenn Ihr Konsument erwartet, dass eine umgebrochene Zeichenkette mit einem Zeilenumbruch endet (manches E-Mail-Tooling tut das), fügen Sie ihn selbst nach der Kodierung hinzu: Das JDK hört absichtlich nach der letzten unvollständigen Zeile auf. Und wenn Sie Daten erzeugen, die in einer URL oder einem Token leben werden, ist der Umbruch der völlig falsche Regler; diese Konsumenten wollen eine lange Zeile und meist kein Padding, was die nächste Sektion ist.

base64url und der No-Padding-Regler

Standard-Base64 endet sein Alphabet mit + und /, und genau das sind die zwei Zeichen, die sich in URLs nicht benehmen: Ein + in einem Query-String ist bereits ein Leerzeichen, bevor der Server ihn je parst, ein / ist ein Pfadtrenner, und ein baumelndes = verlangt die Prozent-Kodierung zu einem dreizeichenlangen Ungeheuer. RFC 4648, Abschnitt 5, zeichnet den Fix: das URL- und Dateinamen-sichere Alphabet, in dem + zu - wird, / zu _, und das =-Padding am Ende typischerweise wegfällt, wenn die Länge implizit bekannt ist. Der RFC ist unerbittlich beim Namen: Diese Kodierung "sollte nicht als gleich mit der base64-Kodierung angesehen werden", und der Name, den Sie hören werden, ist base64url. JSON Web Tokens, OAuth-State-Parameter, API-Session-IDs und elfstellige Video-IDs leben alle in diesem Dialekt.

Java gibt Ihnen das Alphabet mit getUrlEncoder(), aber hier ist der Regler, der Leute fängt: Der URL-sichere Encoder paddet standardmäßig immer noch, und die Token-Standards wollen kein Padding. RFC 7515 ist explizit, dass JWS-Teile base64url verwenden, "wobei alle abschließenden '='-Zeichen weggelassen werden ... und ohne die Aufnahme von Zeilenumbrüchen, Leerzeichen oder anderen zusätzlichen Zeichen". Also ist das kanonische Java-JWT-Rezept eine Zwei-Methoden-Kette:

import java.nio.charset.StandardCharsets;
import java.util.Base64;
public class TokenParts {
  public static void main(String[] args) {
    Base64.Encoder url = Base64.getUrlEncoder().withoutPadding();
    byte[] header = "{\"alg\":\"HS256\",\"typ\":\"JWT\"}".getBytes(StandardCharsets.UTF_8);
    byte[] payload = "{\"sub\":\"1234567890\",\"name\":\"John Doe\"}".getBytes(StandardCharsets.UTF_8);
    System.out.println(url.encodeToString(header));
    // eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9
    System.out.println(url.encodeToString(payload));
    // eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIn0
  }
}

Der withoutPadding()-Aufruf gibt einen neuen Encoder zurück, der sich identisch verhält, außer dass er die abschließenden Pads weglässt; das Original bleibt unangetastet, und die Javadoc sagt es präzise. Die Dekoder-Seite akzeptiert sowohl gepaddete als auch ungepaddete Eingaben, also bleibt ein Wert, den Sie ohne Padding erzeugen, für einen strengen Dekodierer lesbar, weshalb ungepaddet die sichere Wahl ist für alles, was eine API-Grenze überquert. Jetzt ein großer Warnhinweis: Die zwei Teile oben sind die unterschriftslosen Hälften eines JWT. Ein echtes Token braucht eine Signatur, berechnet über "header.payload", und das ist Kryptographie, keine Kodierung. Für Produktion prägen und verifizieren Sie Tokens mit einer JOSE-Bibliothek: JJWT (0.13.0) oder nimbus-jose-jwt (10.9.1). Das API-Artefakt von JJWT ist beispielsweise nur eine Koordinate entfernt:

<dependency>
  <groupId>io.jsonwebtoken</groupId>
  <artifactId>jjwt-api</artifactId>
  <version>0.13.0</version>
</dependency>
<!-- jjwt-impl und jjwt-jackson zur Laufzeit hinzufügen, laut Projektdokumentation -->

YouTube-IDs sind die andere Seite dieses Reglers: elf Zeichen base64url ohne Padding, eine Kennung, die überleben muss, überall dort hineingeklebt zu werden, wo eine URL erlaubt ist. Wenn Ihr System Kennungen erzeugt, die in URLs reisen, ist die withoutPadding()-Kette oben die Form, die Sie kopieren sollten.

Dateien kodieren

Die Alltags-Datei-Aufgabe ist der Spiegel des Lieblings-Beispiels des Dekodierers: eine Datei lesen, sie kodieren, den Text schreiben. Vier Zeilen mit java.nio.file:

import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.Base64;
public class EncodeFile {
  public static void main(String[] args) throws Exception {
    byte[] raw = Files.readAllBytes(Paths.get("report.pdf"));
    String packed = Base64.getEncoder().encodeToString(raw);
    Files.write(Paths.get("report.pdf.b64"), packed.getBytes(StandardCharsets.ISO_8859_1));
    System.out.println(raw.length + " -> " + packed.length());
  }
}

Dieser letzte Print macht die 33-Prozent-Rechnung sichtbar. Eine 1-MB-Datei wird zu rund 1,33 MB Text (4/3 des Originals, plus höchstens zwei Pad-Zeichen), und wenn Sie sie MIME-Style umgebrochen haben, liefern die Zeilenumbrüche noch ein paar Prozent dazu: Die alte Mathematik der Mail-Ära, die immer noch stimmt, ist 4/3 mal 78/76, oder etwa 1,37-mal das Original für eine umgebrochene MIME-Nutzlast. Zwei Konsequenzen. Erstens: Dimensionieren Sie jeden Speicher oder jedes Nachrichtenfeld aus der kodierten Länge, nicht aus der rohen Länge: Eine VARCHAR(255)-Spalte, die freudig einen 192-Byte-Rohwert hält, wird dessen 256-Zeichen-Kodierung ablehnen. Zweitens: Die Kodierungs-Richtung ist die, die den Speicher verschlechtert, also ist die Array-Version für große Dateien das falsche Werkzeug, und die Streaming-Sektion das richtige. Eine kleine Freude für die Datei-Gemeinde: Weil die ersten Ausgabe-Zeichen eine reine Funktion der ersten Eingabe-Bytes sind, beginnt jedes Base64-kodierte PNG mit iVBORw0K und jedes kodierte GIF mit R0lGOD; Sie können den Dateityp erkennen, bevor ein einziges Byte dekodiert wird.

JSON, APIs und Data URIs

Zwei der häufigsten Orte, an denen kodierte Ausgabe auf dem Draht lebt.

Eins: Binär in JSON. Datei-Upload-Endpunkte, Content-APIs, Secret-Stores und Webhooks betten Binärdaten als Base64-Text in JSON ein, denn rohe Bytes würden das JSON-String-Escaping brechen. Die Kodierer-Seite ist an der Grenze ein Einzeiler, und die eine Entscheidung ist, welchen Dialekt die Spezifikation verlangt:

import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.Base64;
public class JsonField {
  public static void main(String[] args) throws Exception {
    byte[] image = Files.readAllBytes(Paths.get("logo.png"));
    // Spezifikation verlangt base64url, ungepaddet:
    String field = Base64.getUrlEncoder().withoutPadding().encodeToString(image);
    // "field" Ihrer JSON-Bibliothek als normalen String-Wert übergeben.
    System.out.println(field.length());
  }
}

Die Falle ist nicht das Kodieren; es ist das Lesen der Spezifikation. Manche APIs wollen Standard-Base64 mit Padding, manche base64url ohne, und ein paar sind bei beiden tolerant. Wenn die Spezifikation schweigt, ist der billigste Fix, sich einen Beispielwert von der anderen Seite anzusehen: Ein - oder _ irgendwo legt das Alphabet fest, und ein abschließendes = legt das Padding fest. Den Dialekt falsch zu treffen, crasht die andere Seite normalerweise nicht; meist wird stattdessen die Datei korrupt, was die langsamste Bug-Art zum Finden ist.

Zwei: Data URIs. Der data:image/png;base64,...-String, der ein Bild in HTML oder CSS inline einbettet, ist die Data URI von RFC 2397: data:, ein optionaler Medientyp, eine optionale ;base64-Flagge, ein Komma und dann die Daten. Eine zu bauen ist String-Konkatenation, und die eine Entscheidung ist, ob die Flagge da ist (keine Flagge heißt, die Nutzlast ist prozentkodierter Text, was niemand für Binärdaten will):

import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.Base64;
public class DataUriBuild {
  public static void main(String[] args) throws Exception {
    byte[] icon = Files.readAllBytes(Paths.get("icon.png"));
    String b64 = Base64.getEncoder().encodeToString(icon);
    String uri = "data:image/png;base64," + b64;
    System.out.println(uri.substring(0, Math.min(40, uri.length())) + "...");
    // data:image/png;base64,iVBORw0KGgo...
  }
}

Der eigene Rat des RFC gilt mit Interesse: Data URIs sind für kurze Werte. Ein 50-KB-Icon inline zu einbetten ist ein normaler Tausch (eine Anfrage weniger); ein 5-MB-Foto inline zu einbetten ist ein Performance-Bug im Komfort-Kostüm. Behalten Sie die Flagge, halten Sie den Medientyp ehrlich, und halten Sie die Bytes klein.

Den Basic-Auth-Header bauen

Der älteste Authentifizierungs-Header im Web ist immer noch der einfachste Base64-Anwendungsfall in Java, denn er ist genau ein Aufruf zum Kodieren. Nach RFC 7617 sendet eine Basic-Anforderung Authorization: Basic gefolgt von der Base64-Kodierung von username:password; das eigene Beispiel des RFC, QWxhZGRpbjpvcGVuIHNlc2FtZQ==, ist "Aladdin:open sesame" in Verkleidung. Auf der Client-Seite ist das Bauen des Headers zwei Zeilen Base64 plus ein moderner HTTP-Aufruf:

import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.nio.charset.StandardCharsets;
import java.util.Base64;
public class BasicAuthClient {
  public static void main(String[] args) throws Exception {
    byte[] credentials = ("alice:secret123").getBytes(StandardCharsets.UTF_8);
    String header = "Basic " + Base64.getEncoder().encodeToString(credentials);
    HttpClient client = HttpClient.newHttpClient();
    HttpRequest request = HttpRequest.newBuilder()
      .uri(URI.create("https://example.com/api/status"))
      .header("Authorization", header)
      .GET()
      .build();
    HttpResponse<String> response = client.send(request, HttpResponse.BodyHandlers.ofString());
    System.out.println(response.statusCode());
  }
}

Drei Vorsichtsdienste gehören zu diesem Header. Erstens: Der RFC ist explizit, dass Basic Kodierung ist und kein Schutz: Die Zugangsdaten sind für jeden lesbar, der die Pakete sehen kann, also ist dieser Header nur so stark wie das HTTPS, das darunter liegt, und er ist eine schlechte Idee auf allem außer TLS. Zweitens: Der Zeichensatz: Der RFC erwartet US-ASCII-Zugangsdaten (für alles andere UTF-8, und der charset-Auth-Parameter ist beratend), also wählen Sie StandardCharsets.UTF_8 und bleiben Sie auf beiden Seiten konsistent. Drittens: Eine Versionsnotiz: Der java.net.http-Client ist aus Java 11; auf einer älteren JVM geht derselbe Header auf einer HttpURLConnection mit einem setRequestProperty-Aufruf, und die Base64-Zeile ist in beiden Fällen identisch. Auf der Server-Seite desselben Headers sind Parsen und Dekodieren das Beispiel des Schwester-Leitfadens, mit der Aufteilung am ersten Doppelpunkt und dem Vergleich in konstanter Zeit. Die zwei Seiten sind zwei Aufrufe derselben API, und das ist die stille Eleganz von diesem.

Werte in Konfigs, Umgebungsvariablen und Spalten

Base64 ist ein Text-Container, deshalb taucht es an Orten auf, die Sie nicht erwarten: eine Datenbank-DSN mit Semikolonen in einer Env-Datei, ein Passwort mit Anführungszeichen in einer Properties-Datei, ein mehrzeiliges Zertifikat in einer Config-Map, ein binärer Blob in einer TEXT-Spalte, weil das Schema designed wurde, bevor jemand an BLOBs dachte. Die Kodierer-Seite ist ein Aufruf, und die ehrliche Einordnung ist, was es ist: ein Format-Sicherheits-Trick, kein Geheimhaltungs-Trick:

import java.nio.charset.StandardCharsets;
import java.util.Base64;
public class ConfigEncode {
  public static void main(String[] args) {
    String dsn = "pg:host=db;password=qu\"ote";
    byte[] raw = dsn.getBytes(StandardCharsets.UTF_8);
    String packed = Base64.getEncoder().encodeToString(raw);
    System.out.println(packed);
    // cGc6aG9zdD1kYjtwYXNzd29yZD1xdSJvdGU=
    System.out.println("DB_DSN_B64=" + packed);
  }
}

Zwei Regeln halten das ehrlich. Erstens: Speichern Sie niemals ein Geheimnis als Base64 und nennen Sie es verschlüsselt: Base64 fügt keine Entropie hinzu und entfernt keine Information, im Moment, in dem ein Entwickler die Datei liest, kann er den Wert mit einem Aufruf dekodieren, und die Sicherheits-Sektion des RFC zeigt auf genau dieses Versagen, Leute, die Zugangsdaten preisgeben, indem sie "kodierte" Protokoll-Austausche einfügen. Wenn der Wert ein Geheimnis ist, verschlüsseln Sie ihn zuerst, und packen Sie den Geheimtext erst dann in Base64, wenn der Kanal Text verlangt. Zweitens: Planen Sie die Größe ein: Der gespeicherte Wert ist etwa ein Drittel größer als das Original, und eine Spalte oder ein Feld, die den Rohwert hielten, halten den kodierten nicht. Und wenn der Wert zurückkommt, dekodieren Sie ihn an der Grenze und halten Sie ihn als Bytes (für Binär) oder als String mit explizitem Zeichensatz (für Text); diese Richtung ist das Terrain des Schwester-Leitfadens.

Streamen für große Daten

Kodieren ist die Richtung, die den Speicher verschlechtert, also geht es bei der Groß-Datei-Geschichte hier darum, den Arbeitssatz klein zu halten. Die Array-Version des Beispiels aus der Dateien-Sektion ist in Ordnung bis zum Punkt, an dem die Datei nicht mehr bequem in den Speicher passt; darüber hinaus ist der Stream-Adapter der richtige Zug. wrap(OutputStream) liefert einen Ausgabe-Stream, der beim Schreiben kodiert, also wird eine Datei von mehreren Gigabyte nie als ein einzelnes Byte-Array gehalten:

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

Es gibt ein Verhalten bei diesem Stream, das ein Highlight verdient, denn die Javadoc selbst weist darauf hin: Der umhüllte Stream kann ein paar übrig gebliebene Bytes intern halten, und die empfohlene Praxis lautet: "Schließen Sie den zurückgegebenen Ausgabe-Stream nach der Verwendung umgehend, wobei er alle verbliebenen Bytes in den zugrunde liegenden Ausgabe-Stream leert". Wenn Sie aufhören zu schreiben und die Ausgabedatei vor dem Schließen lesen, sitzt der Schwanz Ihrer Daten noch im Encoder, und die Datei sieht abgeschnitten aus. Deshalb schließt das Beispiel packed, bevor irgendetwas anderes die Datei berührt, und in Produktion würden Sie beide Streams in einen try-with-resources-Block packen. Holen Sie sich die Gewohnheit: Beim Kodierungs-Stream ist Schließen Teil des Kodierens.

Treffen mit der alten Garde

Geerbte Codebasen sind voll von Base64-APIs, die älter sind als java.util.Base64, und sie zu erkennen erspart Ihnen "Warum bricht mein Output um"-Mysterien. Die vier, die Sie tatsächlich treffen werden:

API Wo Sie sie treffen Was tun
sun.misc.BASE64Encoder / BASE64Decoder Code von vor Java 8 Migrieren zu java.util.Base64; entfernt in Java 9
javax.xml.bind.DatatypeConverter Code aus der XML-Ära, alte Web Services Entfernt in Java 11 (JEP 320); migrieren
org.apache.commons.codec.binary.Base64 Code, der auf JVMs von vor 8 laufen muss Beibehalten für Pre-8-Unterstützung; sonst ist die JDK-Klasse der Standard
com.google.common.io.BaseEncoding Guava-lastige und Big-Data-Stacks Funktioniert einwandfrei; die JDK-Klasse hat keine Abhängigkeiten

Das sun.misc-Paar ist das mit dem Drama. Es war eine interne, nicht unterstützte API (die Art, die auf dem JDK des Tages einwandfrei kompiliert und ohne Deprecation-Warnung verschwindet), und seine Ausgabe hatte eigene Gewohnheiten, wie das Zeilen-Umbrechen des kodierten Textes, woher eine überraschende Anzahl an "Mein Base64 hat Zeilenumbrüche drin"-Bugs kommt. Als Java 9 im September 2017 ausgeliefert wurde, entfernte die Modulsystem-Aufräumaktion es, und der offizielle Migrations-Leitfaden wählt klare Worte: "Insbesondere wurden sun.misc.BASE64Encoder und sun.misc.BASE64Decoder entfernt. Verwenden Sie stattdessen die unterstützte java.util.Base64-Klasse, die in JDK 8 hinzugefügt wurde". Wenn Sie jdeps auf Code ausführen, der die alten Klassen noch referenziert, markiert das Tool die Abhängigkeit als "JDK removed internal API", was so nah an einem Verkehrskegel kommt, wie es das JDK je schafft. Der DatatypeConverter aus JAXB hatte ein längeres, aber ähnliches Leben, deklariert veraltet mit den Java-EE-Modulen in der Java-9-Ära und in Java 11 endgültig entfernt durch JEP 320, "Remove the Java EE and CORBA Modules". Beide Migrationen sind mechanisch: Die alten printBase64Binary- und BASE64Encoder().encode-Aufrufe mappen eins-zu-eins auf getEncoder().encodeToString, abzüglich der Umbruch-Unterschiede, und wenn der Code einmal auf java.util.Base64 läuft, läuft er auf jedem JDK von 8 bis 26, ohne weitere Gedanken.

Sicherheit und Geschwindigkeit

Die Sicherheits-Sektion ist kurz, denn der Job des Encoders kann an Daten nicht scheitern, aber sie ist nicht leer. Base64 ist keine Verschlüsselung, und der Standard sagt es in so vielen Worten: Base-Kodierung "verdeckt optisch Informationen, die sich sonst leicht erkennen ließen, wie Passwörter, bietet aber keine rechnerische Vertraulichkeit", und dieselbe Sektion bemerkt, dass dies "bereits für Sicherheitsvorfälle verantwortlich gewesen ist". Die praktischen Folgerungen für die Kodierer-Seite: Kodieren Sie ein Geheimnis nicht, um es sicher zu machen (es ist jetzt weniger sicher, denn es passt in mehr Kanäle); wenn der Wert ein Geheimnis ist, verschlüsseln Sie zuerst und kodieren Sie den Geheimtext; und behalten Sie den Formbarkeits-Zwilling im Blick, wo ein Empfänger eine gültige Schreibweise gegen eine andere tauschen kann (anderes Padding, Müll in den freien Bits), ohne die dekodierten Daten zu ändern. Ein deterministischer Encoder hilft hier: java.util.Base64 produziert genau eine Ausgabe für genau eine Eingabe, also ist die Schreibweise stabil, wenn Ihr eigenes System einen Wert sowohl schreibt als auch liest, und es sind die externen Werte an der Vertrauensgrenze, die eine Prüfung auf die kanonische Form brauchen.

Bei der Geschwindigkeit hat die Kodierer-Seite dieselbe Geschichte wie die Dekoder-Seite: Auf einer modernen JVM ist die eingebaute Implementation schnell genug, dass Base64 fast nie die Engstelle ist, und sie ist der Referenzpunkt des Benchmarks. Derselbe gRPC-java-Benchmark aus 2025, der im Schwester-Leitfaden erwähnt wird (Issue 11857, JMH auf JDK 17 und 21), setzte den JDK-Encoder auf etwa das 2,5- bis 3,8-fache des Durchsatzes von Guava, mit dem größten Abstand auf x86. Zwei praktische Notizen: Für heiße Pfade teilen Sie eine Encoder-Instanz (die Fabrik gibt bereits dieselbe gemeinsame zurück) und bevorzugen Sie encode(byte[], byte[]) in ein vorab dimensioniertes Array, um die Zuweisung zu überspringen; für riesige Daten ist die Streaming-Sektion die Speicher-Geschichte, und die Kosten des Umbruchs sind Rauschen neben der Festplatte. Die einzige echte Performance-Abgabe in Base64 ist die Größe selbst, und keine Implementation, einschließlich dieser, kann sie verhandeln.

Die Fallen-Checkliste

Jede Falle an einem Ort gesammelt, alle Java-spezifisch:

  • Der fehlende Zeichensatz. text.getBytes() ohne expliziten Zeichensatz verwendet den Plattform-Standard: auf JDK 18+ zufällig richtig, auf allem Älteren falsch, und im Prinzip überall falsch. Übergeben Sie StandardCharsets.UTF_8 und schreiben Sie den Zeichensatz in die Spezifikation.
  • Der gepaddete JWT. getUrlEncoder() paddet standardmäßig, und Tokens wollen kein Padding. Der withoutPadding()-Aufruf ist Teil des Rezepts, kein optionales Extra; ein Token mit abschließendem = ist ein Token, das manche Validatoren ablehnen und manche zerschlagen.
  • Die umgebrochene Ausgabe. Der MIME-Encoder bricht bei 76 mit CRLF um und fügt keinen abschließenden Zeilenumbruch hinzu. Wenn der Konsument einen abschließenden Umbruch erwartet, fügen Sie ihn hinzu; wenn der Konsument gar keine Umbrüche erwartet, verwenden Sie den MIME-Encoder nicht.
  • Die doppelte Kodierung. Einen Wert zu kodieren, der bereits Base64 ist, erzeugt einen völlig gültigen, völlig nutzlosen String. Die klassische Ursache: Ein Feld kommt vor-kodiert aus einer API, und Ihr Code "hilft", es noch einmal zu kodieren. Prüfen Sie, bevor Sie kodieren.
  • Pluszeichen in URLs. Standard-Base64-Ausgabe enthält +, das in einem Query-String ein Leerzeichen ist, bevor der Server es je sieht. Wenn ein Wert im Standard-Alphabet in einer URL reisen muss, kodieren Sie ihn prozentkodiert, oder erzeugen Sie ihn von Anfang an im URL-sicheren Alphabet.
  • Die 33-Prozent-Rechnung. Ein Wert, der in die Roh-Spalte passt, passt nicht in die kodierte. Dimensionieren Sie Speicher, Nachrichtenfelder und Header aus 4 * ceil(n / 3), und denken Sie daran, dass umgebrochene MIME-Ausgabe ein paar Prozent darüber liegt.
  • Der ungeschlossene Stream. Der umhüllte Ausgabe-Stream hält übrig gebliebene Bytes bis zum Schließen. Die Datei vor dem Schließen zu lesen liefert eine abgeschnittene Kodierung. Try-with-resources, jedes Mal.
  • Geheimnisse in freier Sicht. Base64 ist Verpackungsklebeband, kein Schloss. Kodierte Zugangsdaten in einer Konfigurationsdatei, einem Log oder einer Umgebungsvariablen sind lesbare Zugangsdaten. Erst verschlüsseln, oder gar nicht.
  • Die Android-Wand. Auf Android existiert java.util.Base64 erst ab API-Level 26; darunter ist die Framework-Klasse android.util.Base64 mit ihren eigenen Flaggen-Konstanten (NO_PADDING, URL_SAFE und NO_WRAP). Eines davon ohne Check hart zu kodieren bricht genau auf den Geräten, die Sie nie getestet haben.
  • Die Zeilenlängen-Kuriosität. getMimeEncoder(77, ...) bricht still und leise bei 76 um, denn die Länge wird auf ein Vielfaches von vier abgerundet, und eine Anfrage nach 3 oder weniger deaktiviert den Umbruch ganz. Wenn Ihr Format eine ungerade Zeilenlänge verlangt, ist der MIME-Regler nicht das Werkzeug.

Von sun.misc zur Standardbibliothek

Die Java-Geschichte ist eine kurze mit einem klaren Vorher und Nachher. Vor 2014 bekamen Sie, wenn Sie Base64 im JDK brauchten, das interne Paar sun.misc.BASE64Encoder und sun.misc.BASE64Decoder, seit Tag eins nicht unterstützt, mit ihren eigenen 76-Zeichen-Umbruch-Gewohnheiten, oder Sie griffen im XML-Code nach javax.xml.bind.DatatypeConverter, oder Sie fügten Apache Commons Codec oder Guava dem Build hinzu, so kamen viele Enterprise-Codebasen auf drei Base64-Implementierungen und keine Ahnung, welche war welche. Am 18. März 2014 lieferte Java 8 java.util.Base64: eine Klasse, drei Alphabete, die RFC-4648- und RFC-2045-Regeln ordentlich implementiert, das Fabrik-Muster, die Padding- und Umbruch-Regler, und Stream-Adapter in beide Richtungen. Es war das Base64, das die Sprache von Anfang an hätte haben sollen, und die Javadoc sagt seitdem Since: 1.8.

Die Aufräumaktion kam in zwei Wellen. Java 9 (21. September 2017) entfernte das sun.misc-Paar als Teil der Modulsystem-Aufräumaktion, mit dem Migrations-Leitfaden, der jeden Entwickler auf die JDK-8-Klasse verweist, und Java 11 entfernte das JAXB-Modul und seinen DatatypeConverter mit ihm (JEP 320). Java 18 (22. März 2022) brachte JEP 400, "UTF-8 by Default", das Base64 überhaupt nicht anfasste, aber den Fehlermodus der faulen getBytes()-Aufrufe änderte, die es speisen: Der Standard-Zeichensatz der Plattform wurde auf jedem Betriebssystem UTF-8, also hörten alte Mojibake-Muster einfach auf, sich auf neuen JVMs zu reproduzieren. Seit 1.8 hat sich die öffentliche API nicht in einer einzigen Methode geändert. Bewegt hat sich der Motor darunter: Bugfixes und Performance-Arbeit, deshalb finden Community-Benchmarks immer wieder, dass die Standardbibliotheks-Version die Legacy-Bibliotheken, die sie ersetzte, in den Schatten stellt. Heute, auf jedem JDK von 8 bis 26, ist die Antwort auf "Wie mache ich das in Java Base64" ein Import und ein Fabrik-Aufruf, und das ist es seit über einem Jahrzehnt.

Ein paar Nerd-Freuden

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

  • Die Javadoc sagt Since: 1.8, und das ist seit zwölf Jahren wahr. Nicht eine Methode hinzugefügt, nicht eine entfernt, nicht ein Verhalten geändert: Eine der am längsten eingefrorenen API-Oberflächen der Sprache, und Sie benutzen sie, ohne darüber nachzudenken.
  • encodeToString baut seinen Ergebnis-String mit dem ISO-8859-1-Zeichensatz, laut Javadoc. In der Praxis ist es ein völlig unnötiges Detail, denn Base64-Ausgabe ist reines ASCII und sieht in Latin-1, UTF-8 und dem meiste Rest des Zeichensatz-Zoos gleich aus, aber die Javadoc sagt es trotzdem, und das ist das JDK eben.
  • Der MIME-Encoder fügt nach der letzten unvollständigen Zeile keinen Zeilentrenner hinzu. Andere Tools, einschließlich einiger sehr berühmter E-Mail-Bibliotheken, beenden umgebrochene Ausgabe mit einem abschließenden CRLF. Wenn Ihr Diff gegen eine Referenz-Implementation exakt zwei Zeichen am Ende hat, haben Sie diese Kuriosität gefunden.
  • Fragen Sie getMimeEncoder um 77-Zeichen-Zeilen, und es gibt Ihnen 76: Die Zeilenlänge wird still und leise auf das nächste niedrigere Vielfache von vier abgerundet, denn ein Umbruch, der eine Vierer-Gruppe teilt, würde Müll produzieren. Die API verweigert es, eine kaputte Zeile zu bauen, anstatt Ihre Erlaubnis zu fragen.
  • Base64.getEncoder() == Base64.getEncoder() ist wahr. Die Fabrikmethoden geben bei jedem Aufruf dieselbe gemeinsame Instanz zurück, also ist die "einen neuen holen"-API ein Kostüm für einen Singleton, und die Threadsicherheits-Versprechung ist nur eine Beschreibung davon, was die JVM ohnehin schon tut.
  • Auf Android legt die Zwillings-API android.util.Base64 dieselben Entscheidungen als Flaggen offen: NO_PADDING, URL_SAFE, NO_WRAP. Zwei APIs, eine Entscheidungstabelle, was ein stilles Zeugnis dafür ist, wie eingespielt das Base64-Design mittlerweile ist.
  • Abschnitt 5 von RFC 4648 ist der Ort, an dem der Name "base64url" geboren wird: Die Spezifikation sagt, die URL-sichere Kodierung "kann als base64url bezeichnet werden", und warnt, sie "sollte nicht als gleich mit der base64-Kodierung angesehen werden". Ihr Ursprung ist in einer Fußnote einem 2001er-Post auf einer P2P-hackers-Mailingliste zugeschrieben, also hat der Name in jeder URL, die Sie kleben, eine Mailinglisten-Abstammung.
  • Kodieren Sie das Wort base64, und Sie bekommen YmFzZTY0, kein Padding, denn sechs ist ein Vielfaches von drei. Ein Format, das sich selbst beschreibt, ist das technische Äquivalent eines Spiegels, der in Morse spricht, und das hier ist die eigene Spiegelung des Spiegels.
  • Führen Sie jdeps -jdkinternals auf Code von vor Java 8 aus und schauen Sie zu, wie es sun.misc.BASE64Encoder als "JDK removed internal API" markiert. Das Beispiel des Tools im offiziellen Migrations-Leitfaden ist eine Base64-Klasse, was das JDK ist, das auf Ihre Imports zeigt und sagt: "Das haben wir besprochen".
  • Der Faktor 1,37. Jede umgebrochene MIME-Nutzlast kostet etwa 1,37-mal ihre ursprüngliche Größe (4/3 für das Alphabet, 78/76 für den CRLF-Rhythmus), ein Bruchteil so stabil, dass alte E-Mail-Mathematik ihn immer noch zitiert: Die Maut, die die E-Mail-Infrastruktur der 1990er für jeden Anhang erhob, ist exakt die Rechnung, die getMimeEncoder() heute erhebt.

In die andere Richtung

Das ist die Kodierer-Seite der Geschichte, und sie ist die ruhigere von beiden: Der Job scheitert nie an den Daten, die Fallen drehen sich um Ihre Entscheidungen (Zeichensatz, Padding, Umbruch, Dialekt) und nicht um Überraschungen anderer Leute, und die ganze API passt in einen Import. Die andere Richtung ist der Ort, an dem Base64 aufhört, bequem zu sein, und anfängt, feindselig zu sein, denn beim Dekodieren treffen Sie auf die Padding-Entscheidungen anderer Leute, ihre Zeilenumbrüche, ihre Zeichensätze und ihre Armor, mit einer IllegalArgumentException zwischen Ihnen und der Wahrheit. Base64-Dekodierung in Java, von dieser Seite aus verlinkt, behandelt den Dekodierer in derselben Tiefe: die drei Dekodierer-Persönlichkeiten, die exakten Fehlermeldungen, die Padding-Regeln, base64url und JWTs, MIME und PEM, und die Java-spezifischen Fallen, an einem Ort gesammelt. Lesen Sie die beiden als Paar, und das ganze Thema gehört Ihnen.

Zuletzt aktualisiert: 2026-09-08

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