If you write Kotlin, you’ll run into the Regex class sooner or later. Usually it happens while you’re validating a form field or digging through a log file. The class sits in the standard library, wraps java.util.regex on the JVM, and gives you a null-safe, Kotlin-native API for matching and replacing text (and for pulling pieces out of it).
JetBrains maintains it. Most of the daily use comes from Android projects, but the same class runs on Kotlin/JVM backend services too. It also works across Kotlin Multiplatform targets, though the syntax differs a bit by platform.
One version detail is worth knowing. JetBrains’ release notes for Kotlin 1.9.0, published in July 2023, confirm that a shared (common) function for reading a named capture group became available that year. That means multiplatform code can call it directly without platform-specific checks. Before that, each platform had its own version: Kotlin/JVM since Kotlin 1.2, Kotlin/JS since Kotlin 1.7, and Kotlin/Native since the Kotlin 1.7 to 1.8 releases.
What Is Kotlin Regex?

Under the hood, it’s the Regex class in the kotlin.text package. You use it to search, validate, replace and split strings against a pattern, and you don’t have to touch java.util.regex directly.
It isn’t a separate regex dialect made up for the language. The class belongs to the Kotlin standard library, and the pattern syntax comes from whatever platform you’re running on.
Compared to raw pattern matching in Java, most of the gain is in ergonomics. Every matching function is null-safe, you get extension functions like toRegex(), and findAll() returns a Sequence that only computes a match when something actually reads it. I like that last one more than I expected to.
Much of this comes from the language design itself. Knowing what Kotlin is as a language helps explain why its regex support looks the way it does.
A few misconceptions come up a lot:
- It doesn’t have its own pattern syntax. It borrows from the host platform.
- It’s not only for Android, even though that’s where most people first meet it
- And it doesn’t replace simple String functions like
containsorstartsWith, which are often the better tool
Android is still the main reason people learn it. Kotlin’s own documentation says over 50 percent of professional Android developers use Kotlin as their primary language, against 30 percent still naming Java (kotlinlang.org). Android codebases do input validation, log parsing and form checking all the time, so that adds up to a lot of regex.
Outside mobile, the numbers are smaller. In the 2025 Stack Overflow Developer Survey, 11.5 percent of professional developers reported using Kotlin over the past year. Among backend and general-purpose languages, that puts it behind Go (17.4 percent) and Rust (14.5 percent).
How Do You Create a Regex Object in Kotlin?

You either call a constructor or use toRegex() on a String.
Which constructor you pick depends on whether you need options:
Regex(pattern: String)for default matching behaviorRegex(pattern: String, option: RegexOption)when one flag is enoughRegex(pattern: String, options: Set<RegexOption>), which is the one you use to combine flags
There’s nothing unusual here. They work like Kotlin constructors anywhere else in the language. The class just compiles a pattern instead of holding a data model.
I use toRegex() more often, mostly inside pipelines. "^[A-Z]+$".toRegex() skips the extra parentheses a constructor call needs, and it fits better when you’re chaining calls on a list of strings.
Using Raw Strings to Avoid Escaping
A lot of regex bugs come from double-escaped backslashes. Kotlin’s triple-quoted raw strings mostly get rid of them.
In a normal string, matching a literal dot means writing "\\.". Kotlin processes the backslash first, and then the regex engine processes it again. Inside """\.""" you only escape once, because triple quotes turn off Kotlin’s own escape handling.
The worst version of this bug is a pattern that compiles fine and never matches what you meant. If you use raw strings for every pattern, you’ll rarely run into it.
What Regex Syntax Does Kotlin Support?
On the JVM, the syntax comes straight from java.util.regex.Pattern. Character classes, quantifiers and anchors all work the way Java developers expect.
Character Classes and Quantifiers
| Construct | Matches | Example |
|---|---|---|
| \d | Any digit | \d{3} matches 402 |
| \w | Word character | \w+ matches user\_42 |
| + | One or more, greedy | a+ matches aaa |
| +? | One or more, lazy | a+? matches a first, then backs off |
A greedy quantifier grabs as much text as it can and then backtracks. Add a question mark after it and it becomes lazy. It takes as little as possible and only expands when the rest of the pattern needs it to.
Anchors and Word Boundaries
^ pins a match to the start of a line, and $ pins it to the end.
The word boundary, \b, sits between a word character and a non-word character. So \bcat\b matches “cat” on its own but not the “cat” inside “concatenate”. I use it all the time.
Oracle’s Java Platform documentation notes that the underlying Pattern class conforms to Level 1 of Unicode Technical Standard 18. That’s why character classes in Kotlin regex behave consistently with accented letters and non-Latin scripts on the JVM.
What Are RegexOption Values in Kotlin?
You pass these flags into a Regex constructor, one at a time or as a set, to change how matching works. RegexOption is built like any Kotlin enum class. It just holds a handful of matching flags instead of a bigger domain model.
| Option | Effect | Typical Use |
|---|---|---|
| IGNORE\_CASE | Case insensitive matching | User input, usernames, emails |
| MULTILINE | ^ and $ match at every line break | Parsing multi-line log files |
| DOT\_MATCHES\_ALL | . matches newline characters too | Matching across line boundaries |
People often miss this one. Kotlin’s own RegexOption documentation says IGNORE_CASE does Unicode aware case comparison, not a simple ASCII fold, so accented and non-Latin characters fold correctly.
To combine flags, wrap them in a set. Regex(pattern, setOf(RegexOption.IGNORE_CASE, RegexOption.MULTILINE)) applies both.
Which Functions Does Kotlin Regex Provide for Matching Text?

These functions look alike in autocomplete, but they return different things. Picking the wrong one causes real bugs.
| Function | Input Handling | Return Type | Typical Use |
|---|---|---|---|
| matches() | Checks the full string | Boolean | Simple yes or no validation |
| find(startIndex) | Scans from an offset | MatchResult? | First match anywhere in text |
| findAll() | Scans the whole input lazily | Sequence<MatchResult> | Every match, processed one at a time |
| matchEntire() | Requires a full match | MatchResult? | Strict validation with group access |
| containsMatchIn() | Scans anywhere | Boolean | Quick existence check |
matches() and matchEntire() both require the whole input to match the pattern. Only matchEntire() gives you a MatchResult back with the capturing groups attached.
find() and containsMatchIn() also overlap. If you only need the boolean, use containsMatchIn(), since it doesn’t build a MatchResult object you’d throw away.
findAll() returns a lazy Sequence, not a List. Nothing is computed until your code iterates over it.
How Do You Extract Capturing Groups from a Kotlin Regex Match?

After a successful match, MatchResult.groupValues holds the captured text as a plain List<String>, indexed from zero. Index zero is the whole match, and your own groups start at one.
This works for both named and unnamed groups. You can also read named groups by name through MatchResult.groups.
Named Groups
A pattern like (?<year>\d{4})-(?<month>\d{2}) creates two named groups instead of two anonymous ones. You read one with matchResult.groups["year"]?.value, which returns null if that group didn’t take part in the match.
Keep in mind that find() and matchEntire() both return a nullable MatchResult. If you force it with !!, a failed match throws an exception instead of failing gracefully.
The usual approach is to pair that nullable result with Kotlin’s elvis operator. val year = matchResult?.groups?.get("year")?.value ?: "unknown" gives you a fallback in one line, without a separate if block.
For the first several groups, you can also use destructuring. val (year, month) = matchResult.destructured unpacks them straight into named variables, so you don’t have to index into groupValues.
How Do You Validate Input with Kotlin Regex?

An email check and a username check use different patterns, but you set them up the same way:
- Define the pattern as a raw string and store it as a constant outside the function that uses it
- Compile it once into a Regex object instead of rebuilding it on every call
- Call matches() or matchEntire() on the input, depending on whether partial matches should be rejected
- Return a Boolean from the validation function, or throw if the calling code expects strict input
Oracle’s Java Platform documentation is direct about the compile-once step. Calling Pattern.matches() over and over on the same pattern is less efficient than compiling it once and reusing the compiled form.
The same advice applies to Kotlin regex, because a Regex object on the JVM wraps a compiled Pattern under the hood.
I usually put the constant at the top of the file or inside a companion object, separate from the rest of the codebase that calls it. That way, one pattern definition serves every call site.
Take “user@example.com ” with a trailing space. matches() rejects it. matchEntire() is just as strict, but it also returns a MatchResult. That helps when the validation function needs to pull the domain or username back out after confirming the format.
Most teams write and test a function like this directly in Android Studio. They run it against a few known good and known bad inputs before it ships.
How Do You Replace Text Using Kotlin Regex?

Regex.replace() swaps every match for either a literal string or whatever a lambda returns. The lambda receives the full MatchResult for each match, not just the raw matched text.
replace(input, replacement)uses the same literal string for every matchreplace(input, transform)takes a lambda, so each match can get a different result- If only the first match should change, use
replaceFirst(input, replacement)
The transform is a normal Kotlin lambda function. It reads matchResult.value or matchResult.groupValues and returns the replacement string.
Often you don’t need a lambda at all. Backreferences like $1 and $2 in a plain replacement string put the captured group text straight into the output.
There’s one small gotcha. A literal dollar sign in the replacement string has to be escaped as \$. Otherwise the engine reads it as the start of a backreference.
How Do You Split a String Using Kotlin Regex?
Regex.split() breaks a string into a list of pieces wherever the pattern matches, and it drops the separators themselves.
An optional limit parameter caps how many pieces come back. The default is 0, which means no cap.
| Aspect | String.split() | Regex.split() |
|---|---|---|
| Delimiter | Literal substring | Pattern match |
| Limit parameter | Caps result count | Caps result count |
| Best for | Fixed separators, like a comma | Variable width separators |
If you split “a, b, c” on a literal comma, the pieces keep stray spaces. Split on ,\s* instead and the spaces go away in the same step.
A limit of 2 on a string with four possible split points returns exactly two elements. The second one holds everything left over after the first split.
How Does Kotlin Regex Behave Across JVM, JS, and Native Targets?
Each target platform runs Kotlin regex on a different engine, and they don’t read pattern syntax the same way.
Kotlin’s own API documentation says plainly that pattern syntax and the option set differ across platforms. It points developers to the platform-specific pages for the details.
| Platform | Underlying Engine | Known Difference |
|---|---|---|
| Kotlin/JVM | java.util.regex.Pattern | Widest feature set, the reference behavior |
| Kotlin/JS | JavaScript RegExp | Stricter escaping, Unicode flag always on |
| Kotlin/Native | Separate native implementation | Some constructs still diverge from JVM |
If you share validation logic through Kotlin Multiplatform, test it on every target it ships to, not only the one you develop on.
Kotlin/JVM
On the JVM, Regex wraps java.util.regex.Pattern and Matcher directly. Everything covered earlier about syntax, RegexOption values and matching functions applies without changes.
This is the reference implementation that the other two targets are measured against.
Kotlin/JS
Kotlin’s documentation notes that RegExp objects on this target are built with the Unicode “u” flag. That flag blocks some escape sequences the JVM accepts without complaint. In practice, a pattern that works fine on Android can throw a syntax error the first time it runs in a Kotlin/JS build.
The same “u” flag also turns on Unicode aware matching by default. The JVM doesn’t need a flag for that, because Pattern already handles it.
Kotlin/Native
Kotlin’s own Native documentation says outright that matching and replacement behavior on this target may still change to line up more closely with the JVM.
Because of that, teams sharing logic through Kotlin Multiplatform usually stay within a common subset: basic character classes, quantifiers, capturing groups and anchors. Anything more exotic is treated as JVM only. It’s limiting, but it’s easier than tracking down a mismatch that only shows up on Native.
Netflix uses Kotlin Multiplatform to share logic across its mobile studio apps, cutting duplicated code between platforms (Kotlin case studies, kotlinlang.org).
When Should You Use Kotlin Regex Instead of String Functions?

If the text you’re matching can vary in shape, use Regex. If it’s fixed, contains, startsWith or endsWith will do the job.
Regex is worth it when one expression has to handle alternation, character ranges or variable-length pieces, or when you want to capture the matched text through groups. But it costs more than a direct String comparison, both when the pattern compiles and when it matches.
String functions run faster for fixed, literal checks and read clearly when there’s only one condition. Once the condition needs three or four variations, though, you end up with a mess of chained calls.
The speed gap is bigger than most people assume. A JMH benchmark published on GitHub (sigpwned, 2024) found that plain String equality checks ran roughly 30 times faster than compiled Pattern matching on short strings.
The same benchmark found that String-based substring search ran nearly three times faster than the equivalent regex search across a full text.
The gap shrinks for input that really does vary. A hand-written chain of String checks that covers the same cases as one pattern ends up doing similar work anyway.
Checking against a fixed set of three status codes is a job for String functions. Checking whether a string looks like an email is a job for Kotlin regex.
When Does Kotlin Regex Not Apply?
Regex gets risky once a pattern runs against input an attacker controls. It also stops being a safe choice when the pattern has to track nested structure.
The security problem is catastrophic backtracking. It can make a regex match take exponentially longer as the input grows, and it’s the core mechanism behind a ReDoS attack (OWASP Foundation). There are real cases on record:
- CVE-2024-5552 documents an email validation regex in Kubeflow that let crafted input drain server CPU through catastrophic backtracking
- CVE-2024-21490 documents an AngularJS pattern for parsing the ng-srcset attribute that failed the same way against a crafted multi-URL string
Kubeflow is the open source machine learning platform built around Kubernetes. It’s a documented example of a mainstream project affected by exactly this kind of failure.
Nested structure is the other problem. JSON and HTML both allow arbitrarily deep nesting, and a regular expression in the formal sense can’t count matching brackets or tags at unlimited depth.
A dedicated parser handles that nesting correctly. A regex that tries the same job works until a document nests one level deeper than the pattern expected. Then it fails silently or matches the wrong boundary.
If the input format is recursive, I use a parser. If the pattern runs on text that an outside user controls with no length limit, it either gets a length guard or gets replaced.
What Common Mistakes Happen with Kotlin Regex in Practice?
The Regex class itself is rarely the problem. Most bugs come from habits.
The most common one is recompiling inside a loop. A Regex(...) call inside a function that runs once per list item rebuilds the same pattern on every iteration instead of building it once.
Using the wrong function is next. If you call find() where you needed matchEntire(), a string with extra trailing characters passes a check it should have failed.
Group index drift is harder to spot. If you add a capturing group early in a pattern, every groupValues index after it shifts, and any code that reads groups by position breaks. Named groups avoid this, which is one reason I prefer them.
Escaping User Input Safely
If you build a pattern from user-supplied text without escaping it first, ordinary characters like . and ( turn into active regex metacharacters.
Regex.escape(literal), a function on the Regex companion object, turns a raw string into one that matches itself literally, whatever characters it contains. You need it anywhere a pattern is put together at runtime:
- Search boxes that build a pattern from whatever the user typed
- Dynamic filters assembled from database values at runtime
If you skip this step, you’ve got a piece of code refactoring waiting to happen. It usually shows up the first time someone searches for a string that contains a parenthesis.
FAQ on Kotlin Regex
Does Kotlin regex support Unicode character classes?
On the JVM target, yes. Property escapes like \p{L} come directly from java.util.regex.
Support is narrower on Kotlin/JS and Kotlin/Native, where property escapes depend on the underlying JavaScript or native engine.
How do you test or debug a Kotlin regex pattern before shipping it?
Run known good and known bad strings through find() or matches() and print the MatchResult. It’s not exciting, but it works.
IntelliJ IDEA’s Check RegExp intention previews matches inline. For a long pattern, splitting it into named groups shows you which part is failing.
What Should You Prioritize in Kotlin Regex?
Correctness and platform scope matter more than clever syntax. A pattern that handles the common case reliably is better than an elegant one-liner that breaks the first time a real user sends unexpected input through a form.
Before writing a pattern, I confirm which platforms it has to run on and decide what the match will be used for afterward. It’s also worth setting aside maintenance time, because the pattern set will grow.
Two of the figures above point the same way when you put them together. Kotlin’s majority share among professional Android developers, combined with the benchmark showing plain String checks well ahead of compiled Pattern matching, makes Android form validation the most worthwhile place to audit existing patterns. JVM backend services come second.
If you swap a pattern for a String check there, you give up some of the pattern’s one-line flexibility. The natural next step is modeling the validated text as Kotlin data classes instead of passing raw strings deeper into the codebase.
- pip is not recognized in VS Code: Why It Happens and How to Fix It - October 1, 2026
- 10 Enterprise Server Virtualization Platforms for Modern Data Centers - October 1, 2026
- MySQL Cheat Sheet - September 30, 2026



