Android Base64.decode: Kotlin and Java Examples Without the Pitfalls

Android has two Base64 APIs with different defaults, and picking the wrong one produces output that looks right until a payload contains a newline. Here is which to use and why.

Two APIs

android.util.Base64 has been there since API 8 and takes an explicit flags argument. java.util.Base64 arrived with API 26 (Android 8.0) and matches server-side Java code exactly.

If your minSdk is 26 or higher, prefer java.util.Base64 — the same code then works in shared modules and on the JVM. Below that, use the Android one.

The flags that matter

Base64.DEFAULT adds line breaks every 76 characters when encoding. That breaks headers, JSON fields and anything expecting a single line, and it is the single most common Android Base64 bug:

// encode — NO_WRAP, not DEFAULT
val encoded = android.util.Base64.encodeToString(bytes, android.util.Base64.NO_WRAP)

// decode
val bytes = android.util.Base64.decode(encoded, android.util.Base64.NO_WRAP)

// API 26+ — matches server-side Java
val encoded2 = java.util.Base64.getEncoder().encodeToString(bytes)
val bytes2   = java.util.Base64.getDecoder().decode(encoded2)

Decoding is more forgiving than encoding, but only on the Android side. android.util.Base64.decode skips every character outside the alphabet, newlines included, so wrapped input decodes fine and the bug surfaces on the receiving system instead. java.util.Base64.getDecoder() is strict and throws IllegalArgumentException on a line break; use getMimeDecoder() for wrapped input.

base64url and JWTs

Tokens use the URL-safe alphabet with no padding. java.util.Base64.getDecoder() throws on the first - or _. android.util.Base64 with DEFAULT is worse: it skips characters it does not recognise, so - and _ simply vanish and you get wrong bytes without an exception. Pass the URL-safe flag instead:

// Android
android.util.Base64.decode(part, android.util.Base64.URL_SAFE or android.util.Base64.NO_PADDING)

// API 26+
java.util.Base64.getUrlDecoder().decode(part)

Converting bytes to a String should always name the charset: String(bytes, Charsets.UTF_8). Android itself defaults to UTF-8, but code shared with a JVM backend has no such guarantee, because before JDK 18 the JVM default followed the operating system, which on Windows was usually a legacy code page.

Frequently asked questions

Why does my encoded string have newlines?

Base64.DEFAULT wraps at 76 characters. Use Base64.NO_WRAP.

Which API should I use?

java.util.Base64 if minSdk is 26+, otherwise android.util.Base64.

Decoding a JWT part throws — why?

JWTs use base64url without padding, which java.util's basic decoder rejects and android.util's DEFAULT mode silently mangles. Use URL_SAFE with NO_PADDING, or getUrlDecoder().