Searching
contains ยท startsWith ยท indexOfKotlin's String is the same JVM String underneath, but every search operation here is an extension function rather than a Java-style method, and each one has a case-insensitive overload built in โ no separate equalsIgnoreCase-style method to remember for every operation.
val text = "The Quick Brown Fox" println(text.contains("Quick")) // true println(text.contains("quick", ignoreCase = true)) // true println(text.startsWith("The")) // true println(text.endsWith("Fox")) // true println(text.indexOf("Brown")) // 10: character index, or -1 if not found println(text.indexOf("missing")) // -1 // The in operator reads naturally for membership checks println("Quick" in text) // true: same as text.contains("Quick")
Splitting & Joining
split ยท joinToString ยท linessplit takes a delimiter (or several) and returns a List<String> directly โ no need to convert an array to a list afterward, unlike Java's String.split. joinToString is the reverse operation, and it's genuinely more capable than a simple delimiter-glue function: it accepts a prefix, a suffix, and a per-element transform in the same call.
val csv = "Ada,Grace,Alan" println(csv.split(",")) // [Ada, Grace, Alan] println(csv.split(",", limit = 2)) // [Ada, Grace,Alan]: stop after 2 parts // Multiple delimiters in one call println("a, b; c".split(", ", "; ")) // [a, b, c] // lines(): split on line breaks, handling \n and \r\n uniformly val multiline = "line1\nline2\nline3" println(multiline.lines()) // [line1, line2, line3]
val names = listOf("Ada", "Grace", "Alan") println(names.joinToString()) // "Ada, Grace, Alan": default separator ", " println(names.joinToString(" | ")) // "Ada | Grace | Alan" // prefix, postfix, and a per-element transform, all in one call val html = names.joinToString( separator = ", ", prefix = "<ul>", postfix = "</ul>" ) { "<li>$it</li>" } println(html)
Replacing
replace ยท replaceFirst ยท removePrefixPlain replace works against a literal string or a Char; a separate Regex overload of the same function name handles pattern-based replacement (covered below). For the common case of stripping a known prefix or suffix, dedicated functions are clearer than a general-purpose replace.
val text = "the cat sat on the mat" println(text.replace("the", "a")) // "a cat sat on a mat": replaces ALL occurrences println(text.replaceFirst("the", "a")) // "a cat sat on the mat": first occurrence only val fileName = "report_final.pdf" println(fileName.removeSuffix(".pdf")) // "report_final" println(fileName.removePrefix("report_")) // "final.pdf" // removePrefix/removeSuffix are no-ops if the string doesn't // actually start/end with the given text โ no exception thrown println("hello".removeSuffix(".pdf")) // "hello": unchanged
Trimming & Padding
trim ยท trimIndent ยท padStartWhitespace handling splits into two families: trimming removes characters from the ends of a string, while trimIndent/trimMargin deal with the shared leading whitespace of a multi-line raw string block (see raw strings below). Padding does the opposite of trimming โ growing a string to a target length, most often for aligned console output.
val padded = " hello " println("[${padded.trim()}]") // "[hello]" println("[${padded.trimStart()}]") // "[hello ]" println("[${padded.trimEnd()}]") // "[ hello]" // trim accepts specific characters to strip, not just whitespace println("###hello###".trim('#')) // "hello"
val id = "42" println(id.padStart(6, '0')) // "000042" // Common use: aligning a printed table's columns val rows = listOf("Ada" to 36, "Grace" to 85) rows.forEach { (name, age) -> println(name.padEnd(10) + age.toString().padStart(3)) } // Ada 36 // Grace 85
buildString
StringBuilder receiver ยท efficient concatenationBuilding a string piece by piece with repeated += allocates a new String on every append, since strings are immutable (see Performance). buildString gives you a scoped StringBuilder receiver โ using the trailing-lambda-with-receiver pattern from Functions & Lambdas โ so every call inside the block mutates one shared buffer, with a single final String allocated at the end.
val report = buildString { append("Report\n") append("------\n") for (i in 1..3) { append("Line $i\n") } appendLine("Done.") // append + a trailing newline } println(report)
Raw Strings
"""..."""ยท trimMargin ยท trimIndentA triple-quoted raw string treats backslashes literally โ no escaping needed for backslashes, quotes, or newlines โ which is why Basics introduces it for JSON literals and regex patterns. The remaining piece is indentation control: trimIndent strips the smallest common leading whitespace across every line, while trimMargin strips everything up to an explicit marker character you place at the start of each line, giving you more control when the block's natural indentation doesn't line up cleanly.
// trimIndent: strips the common minimum leading whitespace val code = """ fun main() { println("hi") } """.trimIndent() // "fun main() {\n println(\"hi\")\n}" โ the outer 4-space indent is gone, // but the inner 4-space indent (relative) is preserved // trimMargin: an explicit marker (default "|") anchors each line val message = """ |Dear Ada, |Thanks for your order. |Regards, Support """.trimMargin() println(message) // Dear Ada, // Thanks for your order. // Regards, Support
trimIndent by default โ it needs no marker characters cluttering the literal. Switch to trimMargin specifically when the text itself might start a line with meaningful leading whitespace that trimIndent's "smallest common indent" heuristic would otherwise misjudge.Regex
toRegex() ยท matches ยท find ยท replaceKotlin's Regex wraps java.util.regex.Pattern with a friendlier API. Construct one with the Regex(...) constructor or the .toRegex() extension on a string โ a raw string literal is the natural companion here, since regex patterns are already full of backslashes that would otherwise all need escaping.
val phonePattern = """\d{3}-\d{4}""".toRegex() println(phonePattern.matches("555-1234")) // true: entire string must match println(phonePattern.containsMatchIn("call 555-1234 now")) // true: match anywhere // find(): returns the first MatchResult, or null val match = phonePattern.find("call 555-1234 or 555-9999") println(match?.value) // "555-1234": the first match only // findAll(): every match in the string val allMatches = phonePattern.findAll("call 555-1234 or 555-9999") println(allMatches.map { it.value }.toList()) // [555-1234, 555-9999] // Capture groups val datePattern = """(\d{4})-(\d{2})-(\d{2})""".toRegex() val (year, month, day) = datePattern.find("2024-03-15")!!.destructured println("$year/$month/$day") // "2024/03/15" // Regex-based replace, using the string's own replace overload val masked = "call 555-1234 now".replace(phonePattern, "XXX-XXXX") println(masked) // "call XXX-XXXX now"
.destructured property on a MatchResult plugs directly into destructuring declarations (see Classes & Objects), turning capture groups into named variables in one line instead of indexing into match.groupValues[1], [2], [3] by hand.Quick Reference
Syntax cheat-sheet| Function | Signature Shape | Notes |
|---|---|---|
| Substring check | s.contains(x, ignoreCase = true) | Every search function has an ignoreCase overload |
| Position lookup | s.indexOf(x) | Returns -1 if not found, not an exception |
| Split to list | s.split(delim) | Returns List<String> directly |
| Join with control | list.joinToString(sep, prefix, postfix) { } | Per-element transform in the same call |
| Replace all | s.replace(old, new) | Overloaded for String, Char, or Regex |
| Strip known prefix/suffix | s.removePrefix(p) / removeSuffix(p) | No-op (not an error) if it doesn't match |
| Trim whitespace | s.trim() / trimStart() / trimEnd() | Accepts specific chars to strip instead |
| Pad to width | s.padStart(n, char) / padEnd(n, char) | Common for aligned console output |
| Efficient concatenation | buildString { append(...) } | One StringBuilder, one final allocation |
| Multi-line, no escaping | """text""" | Backslashes and quotes are literal |
| Strip common indent | """...""".trimIndent() | Uses the smallest leading whitespace across lines |
| Strip via marker | """...""".trimMargin() | Anchors on a leading "|" (or custom) per line |
| Build a Regex | """pattern""".toRegex() | Raw string avoids double-escaping backslashes |
| Full-string match | regex.matches(s) | The whole string must match, not a substring |
| First match | regex.find(s) | Returns MatchResult? โ null if no match |
| Every match | regex.findAll(s) | Sequence<MatchResult> |
| Capture groups as variables | match.destructured | Destructure directly instead of groupValues[i] |