Strings
Kotlin’s String is the JVM java.lang.String — immutable, 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
joinToStringfor joining collections. - Use
*OrNullvariants (toIntOrNull,toDoubleOrNull) for safe parsing. - Use
equals(other, ignoreCase = true)for case-insensitive comparison. - Use
Regexwith 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.