The answer, briefly: your pattern is backtracking exponentially, and the platform stopped it before it consumed the whole transaction. The message is System.LimitException: Regex too complicated, and it is almost never about the length of your pattern. It is about a specific structural mistake, usually a quantifier inside another quantifier.
Pattern p = Pattern.compile('^(a+)+$');
Matcher m = p.matcher('aaaaaaaaaaaaaaaaaaaaaaaaaaaaX');
m.matches();
// System.LimitException: Regex too complicated
That pattern is thirty characters long against a string of twenty-nine. It fails because of how the engine explores possibilities, not because of size.
What catastrophic backtracking actually is
Apex uses a backtracking regex engine. When a match fails at some position, it goes back and tries a different division of the input among the quantifiers, then tries again.
With (a+)+ there are many ways to split a run of a’s between the inner a+ and the outer +. Three a’s can be grouped as one group of three, a group of one plus a group of two, and so on. Each extra character roughly doubles the number of arrangements. For a string that matches, the engine finds an answer on the first try and everything looks fine. For a string that fails at the very end, it has to rule out every arrangement before giving up.
That is the trap. The pattern works in testing, because test strings match. It falls over on the first input that nearly matches.
The shapes to look for
- Nested quantifiers.
(a+)+,(\d*)*,(.*)+. A group that can repeat, inside something that repeats. - Alternation with overlapping branches, quantified.
(a|aa)+or(\w|\d)+. Both branches match the same characters, so every position has two paths. - Adjacent greedy wildcards.
.*foo.*bar.*. Each.*negotiates with the next over where the boundary falls. - Optional groups next to identical required ones.
(\s*,\s*)?(\s*)and similar things that creep in while making a pattern tolerant of whitespace.
Validation patterns pasted from the internet are a frequent source, email address patterns above all. The elaborate ones circulating for RFC-correct email validation are exactly the shape that explodes on a malformed address, which is the only input you actually care about.
Rewriting
Remove the nesting. (a+)+ and a+ match the same language. The outer quantifier adds nothing but ambiguity. This is the fix in most real cases, and it takes one keystroke.
Anchor the pattern. An unanchored pattern is retried from every starting position, multiplying whatever cost the pattern already has. ^ and $, or \A and \z, cut that away.
Be specific instead of using the dot. Parsing a quoted field, "[^"]*" is both faster and clearer than ".*?", because a negated character class cannot backtrack into territory the next element wants.
Use possessive quantifiers or atomic groups. Apex regex follows Java syntax, so a++, .*+ and (?>...) are available. They tell the engine not to give back what it has consumed, which removes the backtracking entirely. They change what matches, so verify against your cases rather than sprinkling plus signs about.
// ambiguous, backtracks badly on near-misses
'^(\\w+\\s?)+$'
// unambiguous, one way to match any input
'^\\w+(\\s\\w+)*$'
When to stop using regex
A surprising amount of pattern matching in Apex is better served by the String methods, which are faster, cannot throw this exception and are readable a year later:
startsWith,endsWith,containsfor the obvious cases.splitwith a plain delimiter for delimited data. Pass a negative limit if trailing empty values matter.isNumeric,isAlpha,isAlphanumericfor character class checks.Pattern.quotewhen part of your pattern is user data, which stops a stray(becoming a syntax problem.
For genuinely structured input, a parser beats a pattern. JSON has JSON.deserializeUntyped. CSV needs a real parse if fields can contain the delimiter, and every regex that claims to handle quoted CSV is wrong on some input.
The exception is a limit exception, so it behaves like the rest of them: per transaction, uncatchable in the usual sense, and listed alongside the others in the governor limits reference.
Frequently asked
Can I catch it and carry on? It is a LimitException, and limit exceptions are not catchable in the normal way. Assume the transaction is over. Validate the pattern instead of planning to recover from it.
Does input length cause it on its own? A well-formed pattern is roughly linear in input length and will not throw. If a longer input triggers it, the pattern has an ambiguity that longer input makes visible.
Where do I test a rewrite? Anywhere with a Java-compatible engine, since Apex follows Java syntax. Test the strings that nearly match, not the ones that do, because near-misses are where the cost lives.
Is the limit a fixed number of steps? The platform does not publish a figure, and there is no setting to raise it. Treat it as a signal that the pattern needs rewriting rather than a threshold to tune around.
