skip to content

What are RegexOption settings and common correctness/performance pitfalls (escaping, raw strings, catastrophic backtracking) when using Kotlin Regex on untrusted input?

level: principalimportance: nice to knowfreq 30%

answer

  1. Options: IGNORE_CASE, MULTILINE, DOT_MATCHES_ALL, LITERAL, COMMENTS
  2. Raw string """\d""" avoids double-escaping
  3. Regex.escape / escapeReplacement for literals
  4. (a+)+ -> catastrophic backtracking / ReDoS
  5. Compiled Regex is immutable + thread-safe

basics

~10 s

RegexOption tunes matching (ignore case, multiline, dot-matches-newline). Watch escaping in literals, use raw strings, and avoid patterns that can backtrack catastrophically on hostile input.

solid answer

~40 s

Regex accepts RegexOption flags: IGNORE_CASE, MULTILINE (^/$ match line boundaries), DOT_MATCHES_ALL (. matches newlines), COMMENTS, LITERAL, UNIX_LINES, CANON_EQ — pass a single option or a set. Correctness pitfalls: in a normal "..." literal you must double backslashes ("\\d"); raw strings """\d""" avoid that. For matching literal user text, use Regex.escape(text) or RegexOption.LITERAL rather than hand-escaping. Performance/security: nested quantifiers and overlapping alternations like (a+)+ cause catastrophic backtracking (ReDoS) — an adversary can hang a thread with a crafted input. Mitigate by avoiding ambiguous quantifiers, using possessive/atomic constructs or bounded repetition, validating input length, and treating regex over untrusted input as a potential DoS vector. Also reuse compiled Regex instances since they are immutable and thread-safe.

code

kotlin · 6 lines
kotlin
// safe literal matching of user text
fun containsLiteral(haystack: String, needle: String): Boolean =
    Regex(Regex.escape(needle)).containsMatchIn(haystack)

// readable, raw-string pattern with options
val token = Regex("""^\w{3,32}$""", setOf(RegexOption.IGNORE_CASE))

go deeper

for a junior

Knows IGNORE_CASE exists and that backslashes need escaping in string literals.

for a middle

Uses RegexOption sets and raw strings, and knows Regex.escape for literal matching.

for a senior

Recognizes catastrophic backtracking patterns and applies mitigations (atomic groups, bounded repetition, input limits).

for a principal

Treats regex over untrusted input as a DoS threat, sets org-wide guidance (escape helpers, length caps, parser-over-regex for structured formats) and reasons about engine internals.

## RegexOption Flags passed to the constructor/`toRegex` to change matching semantics: - **`IGNORE_CASE`** — case-insensitive matching. - **`MULTILINE`** — `^` and `$` match at line boundaries, not just string ends. - **`DOT_MATCHES_ALL`** — `.` also matches line terminators. - **`COMMENTS`** — whitespace and `#` comments in the pattern are ignored (readable patterns). - **`LITERAL`** — the pattern is treated as plain text, no metacharacters. - **`UNIX_LINES`**, **`CANON_EQ`** — line-terminator and canonical-equivalence handling. ```kotlin val ci = Regex("error", RegexOption.IGNORE_CASE) val multi = Regex("^x", setOf(RegexOption.MULTILINE, RegexOption.IGNORE_CASE)) ``` ## Escaping & raw strings In a normal string literal, a backslash is itself escaped, so a digit class is `"\\d"`. Kotlin **raw strings** (`"""..."""`) take the text verbatim, so `"""\d{3}"""` is cleaner and less error-prone for complex patterns. To match a *literal* user-provided string, never hand-build the pattern — use `Regex.escape(input)` (escapes all metacharacters) or `RegexOption.LITERAL`. `Regex.escapeReplacement(text)` does the same for replacement strings so `$`/`\` aren't interpreted. ## Catastrophic backtracking (ReDoS) Patterns with nested or overlapping quantifiers — `(a+)+`, `(a|a)*`, `(.*)*` — can take exponential time on certain non-matching inputs because the engine tries enormous numbers of partitions. On **untrusted input** this is a denial-of-service vector. Mitigations: - Rewrite to unambiguous patterns; avoid nesting quantifiers. - Use **possessive quantifiers** (`a++`) or **atomic groups** (`(?>...)`), supported by the underlying `java.util.regex`, to prevent backtracking. - Bound repetition (`{0,100}`) and **cap input length** before matching. - Consider timeouts / running matching on input you control; prefer a real parser for structured formats. ## Thread-safety & reuse A compiled `Regex` is immutable and safe to share across threads; declare it once and reuse it to amortize compilation cost.

  • How do you safely match an arbitrary user string literally?
    Wrap it with Regex.escape(text) or use RegexOption.LITERAL so metacharacters are treated as plain text.
  • Name one way to defend against ReDoS in Kotlin/JVM regex.
    Use atomic groups (?>...) or possessive quantifiers (a++), bound repetition, cap input length, and avoid nested quantifiers.

Catastrophic backtracking is like trying every possible way to split a word into syllables before giving up — a few characters can explode into astronomically many attempts.

saying these in an interview costs you the question

  • Building patterns from untrusted input without Regex.escape
  • Unaware that (a+)+-style patterns can hang on hostile input
  • Writing \d in a normal literal instead of \\d or a raw string
  • Believing Regex is mutable/not thread-safe
  • Using regex to parse deeply nested structured formats instead of a parser

context