Polyglot
Languages Kotlin strings
Kotlin § strings

Strings

Kotlin’s String is the JVM java.lang.Stringimmutable, UTF-16 internally, with substantial extension functions admitting Kotlin’s expressive style. The conventional forms: double-quoted (admits escape sequences and template interpolation) and raw ("""""" — multi-line, no escape interpretation, no interpolation by default but admits interpolation). String templates via $variable and ${expression} admit substantial conciseness. The Char type is a 16-bit Unicode unit (supplementary characters require a pair). The kotlin.text package adds substantial extension methods over the Java String API. The combination — JVM-backed strings, the substantial Kotlin extension surface, the template form, the multi-line raw strings, the regex literals — is the substance of Kotlin’s text surface.

String literals

Two principal forms:

val single = "hello"                               // single-line, escape-aware
val multi = """
    line one
    line two
    """.trimIndent()                               // raw multi-line; trimIndent strips common indentation

Escape sequences

Single-line strings admit:

"\n"                                               // newline
"\t"                                               // tab
"\r"                                               // carriage return
"\\"                                               // backslash
"\""                                               // double quote
"\$"                                               // literal $ (escape from interpolation)
"A"                                           // Unicode "A"

Raw strings ("""""") do not interpret escapes:

val raw = """C:\path\to\file"""                    // backslashes are literal
val regex = """\d+"""                              // regex pattern; no double-escape needed

String templates

The conventional Kotlin string-template form:

val name = "Alice"
val greeting = "Hello, $name!"

val age = 30
val description = "$name is $age years old"

// Expression in braces:
val formatted = "Pi: ${Math.PI}, rounded: ${"%.2f".format(Math.PI)}"

// Property access:
val user = User("Alice", 30)
val display = "User: ${user.name}, age ${user.age}"

// Method calls:
val length = "Length: ${"hello".length}"

// Conditional:
val status = "Status: ${if (isActive) "active" else "inactive"}"

The $variable admits simple variable interpolation; ${expression} admits substantial expressions.

String operations

The substantial method surface (a mix of Java and Kotlin extensions):

val s = "hello world"

s.length                                           // 11
s.isEmpty()                                        // false
s.isNotEmpty()                                     // true
s.isBlank()                                        // false
s.isNotBlank()                                     // true

s.contains("world")                                // true
s.startsWith("hello")                              // true
s.endsWith("world")                                // true

s.indexOf("o")                                     // 4
s.lastIndexOf("o")                                 // 7
s.substring(0, 5)                                  // "hello"
s.substring(6..10)                                 // "world"

s.uppercase()                                      // "HELLO WORLD"
s.lowercase()                                      // "hello world"
s.replaceFirstChar { it.uppercase() }              // "Hello world"

s.trim()                                           // strip whitespace
s.trimIndent()                                     // strip common indentation (multi-line)
s.trimStart()
s.trimEnd()

s.replace("world", "Kotlin")                       // "hello Kotlin"
s.replace(Regex("\\s+"), "-")                      // regex replace

s.split(" ")                                       // List<String>: ["hello", "world"]
s.split(Regex("\\s+"))                             // regex split

s.lines()                                          // split on newlines

s.padStart(15)                                     // "    hello world"
s.padEnd(15, '*')                                  // "hello world****"

s.repeat(3)                                        // "hello worldhello worldhello world"
s.reversed()                                       // "dlrow olleh"

s.toCharArray()                                    // CharArray
s.toByteArray()                                    // ByteArray (UTF-8 by default)

Kotlin’s substantial extension surface admits substantial conciseness.

Indexing and char access

val s = "hello"

s[0]                                               // 'h' (Char)
s[s.length - 1]                                    // 'o'

// Iteration:
for (c in s) {
    println(c)
}

// As CharSequence:
s.forEach { println(it) }

// Char range:
for (c in 'a'..'z') print(c)

Char methods

val c: Char = 'A'

c.isLetter()                                       // true
c.isDigit()                                        // false
c.isWhitespace()                                   // false
c.isUpperCase()                                    // true
c.isLowerCase()                                    // false
c.uppercaseChar()                                  // 'A'
c.lowercaseChar()                                  // 'a'

c.code                                             // 65 (Unicode code point)
'A'.code                                           // 65

// To digit:
'5'.digitToInt()                                   // 5
'a'.digitToInt(16)                                 // 10 (hex)

Multi-line strings

The """""" form admits multi-line literals:

val sql = """
    SELECT id, name, email
    FROM users
    WHERE active = true
    """.trimIndent()

val html = """
    <html>
        <body>
            <h1>$title</h1>
        </body>
    </html>
    """.trimIndent()

The trimIndent() strips the common leading whitespace from all lines — admits substantial indentation in source.

For substantial control:

val template = """
    |Hello,
    |  $name
    |Goodbye.
    """.trimMargin()                               // strips up to and including |

The default margin character is |; alternatives admit trimMargin(">").

Regex

Kotlin admits regex through the Regex class and the String.toRegex() extension:

val pattern = Regex("""\d+""")
val s = "abc 123 def 456"

pattern.containsMatchIn(s)                         // true
pattern.find(s)?.value                             // "123"
pattern.findAll(s).map { it.value }.toList()       // ["123", "456"]

s.replace(Regex("""\s+"""), "-")                   // "abc-123-def-456"

// Named groups (since 1.5):
val date = "2026-01-15"
val datePattern = Regex("""(?<year>\d{4})-(?<month>\d{2})-(?<day>\d{2})""")
val match = datePattern.find(date)
match?.groups?.get("year")?.value                  // "2026"

// Match all:
"foo bar baz".split(Regex("""\s+"""))              // ["foo", "bar", "baz"]

The conventional discipline uses raw strings ("""""") for regex patterns to avoid double-escaping.

Conversion

// Number to String:
val s = 42.toString()                              // "42"
val s = 3.14.toString()                            // "3.14"
val s = "$42"                                      // "$42" (interpolation)

// String to Number:
val n = "42".toInt()                               // 42 (throws on failure)
val n = "42".toIntOrNull()                         // Int? (null on failure)
val d = "3.14".toDouble()
val d = "3.14".toDoubleOrNull()
val l = "100".toLong()
val b = "true".toBoolean()
val b = "true".toBooleanStrict()                   // throws on non-true/false

// Char / String:
val c: Char = 'A'
val s = c.toString()                               // "A"
val first: Char = "Alice".first()                  // 'A'

// CharArray:
val chars = "hello".toCharArray()
val s = chars.concatToString()

The conventional defence for parsing is the *OrNull variants — admit explicit null handling rather than exceptions.

Format strings

The Java-style format is admitted as an extension:

"%d + %d = %d".format(2, 3, 5)                    // "2 + 3 = 5"
"%.2f".format(Math.PI)                             // "3.14"
"%5d".format(42)                                   // "   42" (right-padded)
"%-5d".format(42)                                  // "42   " (left-padded)
"%05d".format(42)                                  // "00042" (zero-padded)
"%x".format(255)                                   // "ff" (hex)
"%b".format(true)                                  // "true"

// Multiple args:
"%s is %d years old".format("Alice", 30)

The conventional Kotlin discipline uses string templates over format for routine cases; format for substantial precision control.

Common patterns

Building a substantial string

// Inefficient (each += allocates):
var s = ""
for (item in items) {
    s += item.toString() + "\n"
}

// With buildString:
val s = buildString {
    for (item in items) {
        appendLine(item)
    }
}

// With joinToString:
val s = items.joinToString("\n") { it.toString() }
val s = items.joinToString(separator = ", ", prefix = "[", postfix = "]")

The buildString admits substantial efficient construction; the joinToString admits substantial flexibility for delimiter handling.

Multi-line raw string

val template = """
    Hello, $name!

    Your account balance is: ${"%.2f".format(balance)}

    Sincerely,
    The Team
    """.trimIndent()

Regex parsing

val log = "2026-01-15T10:00:00.123 INFO Application started"
val regex = Regex("""(?<timestamp>[^\s]+)\s+(?<level>\w+)\s+(?<message>.*)""")

val match = regex.find(log)
if (match != null) {
    val timestamp = match.groups["timestamp"]?.value
    val level = match.groups["level"]?.value
    val message = match.groups["message"]?.value
}

String validation

fun isValidEmail(s: String): Boolean {
    return s.matches(Regex("""^[^\s@]+@[^\s@]+\.[^\s@]+$"""))
}

fun isNumeric(s: String): Boolean {
    return s.toIntOrNull() != null
}

Padding for tabular output

data class Row(val name: String, val age: Int)

val rows = listOf(Row("Alice", 30), Row("Bob", 25), Row("Charlie", 35))

for (row in rows) {
    println("${row.name.padEnd(10)} ${row.age.toString().padStart(3)}")
}
// Alice         30
// Bob           25
// Charlie       35

Splitting and rejoining

"a,b,c".split(",")                                 // ["a", "b", "c"]
"a,b,,c".split(",")                                // ["a", "b", "", "c"]
"a,b,c".split(Regex(","))                          // also admitted

listOf("a", "b", "c").joinToString(", ")           // "a, b, c"

Case-insensitive comparison

"Hello".equals("hello", ignoreCase = true)         // true
"Hello".compareTo("hello", ignoreCase = true)      // 0

Substring extraction

val s = "name: Alice, age: 30"

// Between markers:
val name = s.substringAfter("name: ").substringBefore(", ")  // "Alice"
val age = s.substringAfter("age: ").toInt()                  // 30

// Or with regex:
val match = Regex("""name: (\w+), age: (\d+)""").find(s)!!
val (matchedName, matchedAge) = match.destructured

URL encoding/decoding

import java.net.URLEncoder
import java.net.URLDecoder

val encoded = URLEncoder.encode("hello world", "UTF-8")    // "hello+world" or "hello%20world"
val decoded = URLDecoder.decode("hello%20world", "UTF-8")  // "hello world"

Hex encoding

val bytes = "hello".toByteArray()
val hex = bytes.joinToString("") { "%02x".format(it) }
// "68656c6c6f"

val hexToBytes = "68656c6c6f".chunked(2).map { it.toInt(16).toByte() }.toByteArray()
String(hexToBytes)                                 // "hello"

Reading lines from stdin

val input = readLine() ?: ""                       // single line
val n = readLine()?.toIntOrNull() ?: 0

generateSequence(::readLine).forEach { line ->     // until EOF
    process(line)
}

Custom format

fun Double.formatCurrency(): String = "$%.2f".format(this)
fun Int.formatThousands(): String = "%,d".format(this)

println(1234.567.formatCurrency())                 // "$1234.57"
println(1234567.formatThousands())                 // "1,234,567"

The pattern admits substantial domain-specific formatting through extension functions.

Heredoc-style with leading pipe

val message = """
    |Hello, $name!
    |
    |This is a multi-line message.
    |
    |Sincerely,
    |The Team
    """.trimMargin()

The | admits explicit margin marking — substantial for content where leading whitespace matters.

A note on Char vs String

In Kotlin, Char and String are distinct types:

val c: Char = 'A'                                  // single character (single quotes)
val s: String = "A"                                // single-character string (double quotes)

c.toString() == s                                  // true (after conversion)
c.code                                             // 65
s.length                                           // 1

The distinction admits substantial type safety — methods that operate on individual characters take Char; methods on strings take String.

A note on the conventional discipline

The contemporary Kotlin strings advice:

  • Use string templates ($var, ${expr}) for interpolation.
  • Use """""" for multi-line strings; trimIndent() for indentation.
  • Use raw strings ("""""") for regex patterns.
  • Use buildString { } for substantial concatenation.
  • Use joinToString for joining collections.
  • Use *OrNull variants (toIntOrNull, toDoubleOrNull) for safe parsing.
  • Use equals(other, ignoreCase = true) for case-insensitive comparison.
  • Use Regex with named groups for substantial parsing.
  • Use the substantial extension surface (isBlank(), padEnd, chunked, etc.).
  • Use extension functions for domain-specific formatting.

The combination — JVM-backed immutable strings, the template form for interpolation, the multi-line raw form, the substantial Kotlin extension surface, the regex integration — is the substance of Kotlin’s text surface. The discipline produces concise, expressive text handling with substantial built-in functionality.