Закодировать и декодировать строку
java.util.Base64 работает с байтами. Преобразуйте строки с явно указанной кодировкой: getBytes() без аргументов берёт кодировку платформы по умолчанию, а до Java 18 это не всегда был UTF-8.
import java.nio.charset.StandardCharsets;
import java.util.Base64;
String enc = Base64.getEncoder()
.encodeToString("héllo wörld".getBytes(StandardCharsets.UTF_8));
System.out.println(enc); // aMOpbGxvIHfDtnJsZA==
String dec = new String(Base64.getDecoder().decode(enc), StandardCharsets.UTF_8);
System.out.println(dec); // héllo wörld
Экземпляры Base64.Encoder и Base64.Decoder потокобезопасны, их можно хранить в поле static final.
URL-safe Base64 с дополнением и без
getUrlEncoder() использует - и _. Дополнение включено по умолчанию; withoutPadding() возвращает новый кодировщик, который его не пишет:
byte[] d = "<<???>>".getBytes(StandardCharsets.UTF_8);
Base64.getUrlEncoder().encodeToString(d); // PDw_Pz8-Pg==
Base64.getUrlEncoder().withoutPadding().encodeToString(d); // PDw_Pz8-Pg
new String(Base64.getUrlDecoder().decode("PDw_Pz8-Pg"), StandardCharsets.UTF_8);
// <<???>>
Декодеры принимают данные и с дополнением, и без, поэтому getDecoder().decode("aGk") тоже сработает и вернёт hi. Сегменты JWT – это Base64URL без дополнения; что и когда использовать, объясняет статья Base64 и Base64URL.
MIME-кодировщик и декодер
getMimeEncoder() пишет строки по 76 символов, разделённые \r\n, как требует RFC 2045. После последней строки перевод строки не добавляется.
String mime = Base64.getMimeEncoder().encodeToString("x".repeat(60).getBytes());
// eHh4eHh4...eHh4 (76 chars) + "\r\n" + "eHh4"
Base64.getMimeEncoder(64, "\n".getBytes()); // PEM-style 64-char lines
Длина строки округляется вниз до кратной 4. getMimeDecoder() – самый снисходительный: он игнорирует разделители строк и любые другие символы вне алфавита.
Base64.getMimeDecoder().decode("aGVs\r\nbG8K!"); // bytes of "hello\n"
Кодировщик MIME Base64 выдаёт такой же формат в браузере.
Закодировать и декодировать файл
Небольшие файлы:
import java.nio.file.*;
byte[] file = Files.readAllBytes(Path.of("hello.txt"));
System.out.println(Base64.getEncoder().encodeToString(file)); // aGVsbG8K
Files.write(Path.of("copy.txt"), Base64.getDecoder().decode("aGVsbG8K"));
Большие файлы потоком через wrap()
Encoder.wrap(OutputStream) и Decoder.wrap(InputStream) кодируют и декодируют на лету, так что расход памяти не зависит от размера файла:
try (InputStream in = new FileInputStream("big.bin");
OutputStream out = Base64.getEncoder().wrap(new FileOutputStream("big.b64"))) {
in.transferTo(out);
}
try (InputStream in = Base64.getDecoder().wrap(new FileInputStream("big.b64"));
OutputStream out = new FileOutputStream("big.out")) {
in.transferTo(out);
}
Закрыть оборачивающий поток обязательно: последнюю группу и дополнение кодировщик пишет в close(). Забудете – и пропадёт последняя группа: до четырёх символов вместе с дополнением. На тестовом файле в 300 КБ результат в точности совпал с encodeToString, а после кодирования и декодирования байты остались идентичными. InputStream.transferTo требует Java 9; в Java 8 копируйте циклом с буфером.
Проверка и исключения
Базовый и URL-декодер строгие и бросают java.lang.IllegalArgumentException. Сообщение говорит, что пошло не так, а символы выводятся в hex:
Вход для getDecoder() | Сообщение |
|---|---|
PDw_Pz8-Pg== | Illegal base64 character 5f |
"aGVs\nbG8K" | Illegal base64 character a |
aGk=!! | Input byte array has incorrect ending byte at 4 |
aGk== | Input byte array has incorrect ending byte at 4 |
a | Input byte[] should at least have 2 bytes for base64 bytes |
Так что проверка сводится к try/catch:
static boolean isBase64(String s) {
try {
Base64.getDecoder().decode(s);
return true;
} catch (IllegalArgumentException e) {
return false;
}
}
Одна брешь: декодер принимает ненулевые биты дополнения, поэтому aGl= без возражений декодируется в hi. Если нужны только канонические строки, закодируйте результат заново и сравните. Валидатор Base64 показывает, какой символ мешает, а в статье частые ошибки Base64 разобран каждый тип сбоев.
Частые подводные камни
- Кодировка платформы.
"text".getBytes()иnew String(bytes)используют кодировку по умолчанию. В Windows до Java 17 включительно это обычно была Windows-1252, которая ломает текст не в ASCII. С Java 18 по умолчанию UTF-8 (JEP 400), но явныйStandardCharsets.UTF_8ничего не стоит. - Не тот декодер для входных данных. URL-safe строка в базовом декодере даёт
Illegal base64 character 5fили2d. Строка в формате MIME –Illegal base64 character aилиd. encode()иencodeToString().encode()возвращаетbyte[]из ASCII-символов. Если напечатать такой массив, увидите что-то вроде[B@1b6d3586, а не Base64.- Незакрытый обёрнутый поток. Без
close()вывод кодировщика обрезан. - Надежда, что Base64 скрывает данные. Не скрывает; см. кодирование и шифрование.
Версии
- Java 8: появился
java.util.Base64с базовым, URL и MIME вариантами,withoutPadding()иwrap(). - Java 9:
InputStream.transferTo, которым мы копируем потоки выше. - Java 11:
javax.xml.bind.DatatypeConverter.printBase64Binaryпропал вместе с удалением JAXB из JDK. - Java 18: UTF-8 стал кодировкой по умолчанию.
В Kotlin доступно всё перечисленное, а кроме того, там есть собственный мультиплатформенный API, о нём рассказано на странице Base64 в Kotlin.
Вопросы и ответы
Что значит «Illegal base64 character 5f»?
Число – это проблемный символ в hex. 5f – это _, а 2d – -: значит, вы декодируете URL-safe Base64 через getDecoder(). Возьмите getUrlDecoder(). a – перевод строки, 20 – пробел; используйте getMimeDecoder() или удалите пробельные символы.
Стоит ли ещё пользоваться sun.misc.BASE64Encoder или javax.xml.bind.DatatypeConverter?
Нет. sun.misc.BASE64Encoder – внутренний API, которого нет в современных JDK, а javax.xml.bind убрали из JDK в Java 11. java.util.Base64 заменяет оба.
Принимает ли декодер Java Base64 без дополнения?
Да. Base64.getDecoder().decode("aGk") возвращает байты hi. Декодеры допускают отсутствие дополнения, но отвергают неправильное дополнение.
Как закодировать в Base64 на Android?
С API level 26 доступен java.util.Base64. Код постарше использует android.util.Base64.encodeToString(bytes, Base64.NO_WRAP); без NO_WRAP он добавляет переносы строк.