Kotlin Regex Guide
Kotlin's Regex class is a clean wrapper over Java's java.util.regex engine, so the pattern syntax is the mature Java one, but the API is idiomatic Kotlin: nullable MatchResults, sequences, and lambdas. A few Kotlin-specific points — triple-quoted patterns, the difference between matches, containsMatchIn, and find, and how to read named groups — trip up developers arriving from other languages. This guide is a practical reference. Pair it with the regex tester and the explainer to confirm any pattern you write.
Short answer: Kotlin's Regex wraps Java's regex engine, so the pattern syntax is Java's but the API is idiomatic Kotlin. Write patterns as triple-quoted raw strings ("""\d+""") to avoid double-escaping, use containsMatchIn to search and matches only for a full-string match, and read named groups with result.groups["name"]?.value.
Prefer Triple-Quoted Strings
Kotlin string literals process backslash escapes (\n, \t, \uXXXX) before the regex engine ever runs, so a literal backslash in a pattern has to be doubled. Triple-quoted raw strings turn that off:
val digits = Regex("\\d+") // ordinary string: backslash must be doubled
val digits = Regex("""\d+""") // raw string: write the pattern as-is
Always reach for """...""" for anything beyond the most trivial pattern — it is the direct equivalent of Python's r"..." and eliminates the backslash-doubling mistakes that plague ordinary-string patterns.
The Regex Functions
You can build a Regex and call its methods, or use the String extensions. The core operations:
val re = Regex("""\d+""")
re.find("abc 42 xyz")?.value // "42" — first match, or null
re.findAll("1 2 3").map { it.value } // sequence: "1", "2", "3"
re.containsMatchIn("abc 42") // true — matches anywhere (search)
re.matches("42") // true — whole string must match
re.matchEntire("42")?.value // "42" — full match as MatchResult?
matches vs containsMatchIn vs find
This is the number-one Kotlin regex gotcha. matches requires the entire input to match — it is a full-match test, not a search. To ask "does this appear anywhere," use containsMatchIn; to pull the match out, use find. Developers coming from languages where the default operation searches often reach for matches and are surprised when Regex("""\d+""").matches("abc 42") returns false — because the whole string is not digits. Use matches or matchEntire deliberately, for validation.
Regex Options
Flags are passed as RegexOption values, singly or as a set:
Regex("hello", RegexOption.IGNORE_CASE)
Regex("""^\w+""", setOf(RegexOption.IGNORE_CASE, RegexOption.MULTILINE))
The common options are IGNORE_CASE, MULTILINE (^ and $ match at line breaks), DOT_MATCHES_ALL (. also matches newlines), and COMMENTS (see below).
Inline Flags
Because the engine is Java's, you can also embed flags in the pattern: (?i) at the start turns on case-insensitivity for the whole pattern, and (?i:...) scopes it to one group. That is useful when the pattern string comes from configuration and you cannot pass a RegexOption.
Comments Mode for Readable Patterns
RegexOption.COMMENTS (Java's "extended" or verbose mode) ignores unescaped whitespace and # comments in the pattern, so you can format a long regex across lines:
val date = Regex("""
(\d{4}) # year
-(\d{2}) # month
-(\d{2}) # day
""", RegexOption.COMMENTS)
Whitespace inside a character class [ ] is still literal, and you can match a literal space with \s or an escaped space. Comments mode plus a triple-quoted string is the readable way to write any pattern longer than a line.
Named Groups
Name a group with (?<name>...) and read it back by name:
val re = Regex("""(?<year>\d{4})-(?<month>\d{2})""")
val m = re.find("2026-07")!!
m.groups["year"]?.value // "2026"
m.groups["month"]?.value // "07"
groups["name"] returns a nullable MatchGroup?, so use a safe call. Named-group access by name is a JVM feature; verify it on your target if you are writing multiplatform Kotlin.
findAll and Capture Groups
Each MatchResult exposes groupValues (index 0 is the whole match, 1+ are the groups) and supports destructuring, which reads cleanly in a loop:
val re = Regex("""(\w+)=(\d+)""")
for (m in re.findAll("a=1 b=2")) {
val (key, value) = m.destructured
println("$key -> $value") // a -> 1, then b -> 2
}
findAll returns a lazy Sequence, so it plays well with map, filter, and friends without building an intermediate list.
replace With a Lambda
Kotlin's replace has an overload that takes a transform function, giving you the match to compute each replacement — the equivalent of Python's re.sub with a callback:
val masked = Regex("""\d""").replace("card 4111") { "*" } // "card ****"
val upper = Regex("""\w+""").replace("hi there") { it.value.uppercase() } // "HI THERE"
Inside the lambda, it is the MatchResult, so you can reach its groups. For literal replacements you can use group references like $1 in the string overload instead.
Lookahead and Lookbehind
Because the engine is Java's, lookahead (?=...) / (?!...) and lookbehind (?<=...) / (?<!...) all work, with the Java caveat that lookbehind must be bounded (finite length) — a lookbehind of unbounded length such as (?<=a+) is rejected. See the lookahead and lookbehind guide for a full treatment of zero-width assertions.
Common Gotchas
matchesis full-match, not search. Reach forcontainsMatchInorfindunless you truly want the whole string to match.- Backslashes in ordinary strings.
"\d"is a compile error class of bug (invalid escape); use"""\d"""or"\\d". findreturns null. The result isMatchResult?— handle the no-match case with?.or?:rather than!!.- Named groups are JVM-first. Access by name works on the JVM; confirm on Native/JS for multiplatform code.
- Recompiling in a loop. Hoist
val re = Regex(...)out of hot loops so the pattern is compiled once, not on every iteration.
Try Patterns Live
Kotlin's pattern syntax is Java's, so any pattern you prototype in a generic tester will behave the same in Regex. Build and check your expression in the regex tester, break down an unfamiliar one with the regex explainer, and keep the regex cheat sheet handy for syntax.
Frequently Asked Questions
Why should I use triple-quoted strings for Kotlin regex patterns?
Because ordinary Kotlin string literals interpret backslash escapes like \n and \t before the regex engine sees them, so a literal backslash in a pattern must be doubled — "\\d+" to match digits. Triple-quoted (raw) strings, """\d+""", disable escape processing and pass the backslash through verbatim, exactly like Python's r"..." strings. Use """...""" for any non-trivial pattern; it removes a whole class of backslash bugs. The one thing raw strings cannot contain is an escaped quote, but that rarely matters in a regex.
What is the difference between matches, containsMatchIn, and find in Kotlin?
matches(input) (and String.matches(regex)) returns true only if the entire string matches the pattern — it is a full-match test, like Python's fullmatch. containsMatchIn(input) returns true if the pattern matches anywhere in the string — this is the "search" behaviour most people actually want. find(input) returns the first match as a nullable MatchResult? (or null), and findAll(input) returns a Sequence of all matches. The classic bug is using matches when you meant containsMatchIn, then wondering why a valid substring does not match.
How do I read a named group in Kotlin?
Define the group with (?<name>...) in the pattern, then read it from the match with result.groups["name"]?.value. The groups["name"] access returns a nullable MatchGroup?, so use a safe call or elvis operator in case the group did not participate in the match. Note that named-group access by name is a JVM feature; on Kotlin/Native and Kotlin/JS the named-group API differs, so if you are multiplatform, confirm support on your target.
How do I make a Kotlin regex case-insensitive?
Pass a RegexOption: Regex("hello", RegexOption.IGNORE_CASE), or combine several with setOf(RegexOption.IGNORE_CASE, RegexOption.MULTILINE). You can also embed an inline flag at the start of the pattern, (?i)hello, or scope it to part of the pattern with (?i:hello), since the underlying Java engine supports inline flag groups. The enum options are the idiomatic Kotlin choice; inline flags are handy when the pattern comes from configuration and you cannot pass options.
Is Kotlin regex the same as Java regex?
On the JVM, yes at the engine level — Kotlin's Regex delegates to java.util.regex.Pattern, so the pattern syntax, supported features (lookahead, bounded lookbehind, backreferences, named groups), and quirks are Java's. What differs is the API surface: Kotlin gives you a nullable MatchResult? instead of a stateful Matcher, Sequence-based iteration via findAll, destructuring of capture groups, and a replace overload that takes a lambda. So learn Java's pattern rules, but write against Kotlin's friendlier API.