Skip to content

Base64 Encode / Decode — Kotlin source

Encode text to Base64 or decode it back. UTF-8 safe, runs entirely in your browser, with a shareable link to your exact input.

This is the Kotlin implementation — the same logic the interactive tool runs, in a shareable, citable form.

// base64 — UTF-8 safe Base64 encode/decode.
//
// Language: Kotlin (1.9, JVM, standard library only — java.util.Base64 is
//           the RFC 4648 codec; kotlin.io.encoding.Base64 is the
//           multiplatform equivalent but is still @ExperimentalEncodingApi
//           in 1.9)
// Source:   CosmoDev polyglot showcase port of the `base64` tool, ported
//           from src/lib/base64.ts (the canonical TypeScript implementation).
// License:  display source — part of CosmoDev's polyglot tool pages.
//
// The JVM already speaks Base64, so — like python.py — this port leans on the
// standard library instead of re-deriving the 6-bit packing rust.rs
// hand-rolls. Two details keep it faithful to the TS contract: whitespace is
// stripped with the same \s+ regex before decoding, and decoded bytes run
// through decodeToString(throwOnInvalidSequence = true), because plain
// String(bytes) would silently substitute U+FFFD for malformed UTF-8.
//
// Run: kotlinc kotlin.kt -include-runtime -d base64.jar && java -jar base64.jar

import java.nio.charset.CharacterCodingException
import java.util.Base64

/** Encode a Unicode string to standard (padded) Base64 over its UTF-8 bytes. */
fun b64encode(text: String): String =
    Base64.getEncoder().encodeToString(text.toByteArray(Charsets.UTF_8))

/**
 * Decode standard Base64 back to the original Unicode text.
 *
 * Whitespace inside the input is stripped first (the TS port's `\s+` regex),
 * so line-wrapped Base64 decodes cleanly. Any malformed input — illegal
 * characters, bad padding, or decoded bytes that are not valid UTF-8 —
 * throws [IllegalArgumentException], matching the TS port's contract.
 */
fun b64decode(b64: String): String {
    val cleaned = b64.replace(Regex("\\s+"), "")

    val raw = try {
        Base64.getDecoder().decode(cleaned)
    } catch (e: IllegalArgumentException) {
        throw IllegalArgumentException("invalid Base64 input: ${e.message}", e)
    }

    return try {
        raw.decodeToString(throwOnInvalidSequence = true)
    } catch (e: CharacterCodingException) {
        // Malformed UTF-8 surfaces as CharacterCodingException here, unlike
        // the illegal-character case above.
        throw IllegalArgumentException("decoded bytes are not valid UTF-8", e)
    }
}

fun main() {
    println(b64encode("Hello, world!"))            // SGVsbG8sIHdvcmxkIQ==
    println(b64decode("aGVs\nbG8g d29ybGQ="))     // hello world
    println(b64decode(b64encode("héllo 🌍")))      // multi-byte UTF-8 survives

    try { b64decode("SGVsbG8*") } catch (e: IllegalArgumentException) { println(e.message) }

    // "/w==" decodes to the single byte 0xFF, which is not valid UTF-8.
    try { b64decode("/w==") } catch (e: IllegalArgumentException) { println(e.message) }
}

Also available in 13 other languages

Every CosmoDev tool ships its pure logic in TypeScript (web) and Go (CLI), with authored implementations in a dozen-plus languages — the same contract, ported. Compare all languages side by side →