A pattern that works in the browser can fail to compile in Python, compile in Java and match something different, or pass every test except the one input with a trailing newline. The syntax overlaps enough that the differences catch you off guard. This guide covers the ones that cause real bugs, each checked on Node 24, CPython 3.12 and 3.14, and OpenJDK 21, with notes where older versions differ.
The regex tester runs your pattern on your browser's own JavaScript engine, so what it shows is JavaScript behaviour. Get the pattern right there, then translate it with the notes below before it goes into Python or Java.
The short version
JavaScript Python re Java
named group (?<id>…) (?P<id>…) (?<id>…)
named backreference \k<id> (?P=id) \k<id>
named in replacement $<id> \g<id> ${id}
lookbehind any pattern fixed width bounded length
whole-string match ^(?:…)$ re.fullmatch() matcher.matches()
$ before a final \n no yes yes
\d and \w ASCII Unicode ASCII by default
\p{L} with u or v flag not in re yes
atomic, possessive no 3.11 and later yes
inline (?i) (?i:…) only, ES2025 start of pattern anywhere
Named groups and backreferences
JavaScript (since ES2018) and Java write a named group as (?<name>…). Python's re module uses (?P<name>…) and rejects the other spelling: as of Python 3.14, re.compile(r"(?<y>\d{4})") fails with unknown extension ?<y. Java and JavaScript reject the P form in turn.
// JavaScript
const m = /(?<year>\d{4})-(?<month>\d{2})/.exec("2026-10");
m.groups.year // "2026"
"2026-10".replace(/(?<y>\d{4})-(?<m>\d{2})/, "$<m>/$<y>") // "10/2026"
# Python
m = re.search(r"(?P<year>\d{4})-(?P<month>\d{2})", "2026-10")
m.group("year") # '2026', also m["year"]
re.sub(r"(?P<y>\d{4})-(?P<m>\d{2})", r"\g<m>/\g<y>", "2026-10") # '10/2026'
// Java
Matcher m = Pattern.compile("(?<year>\\d{4})-(?<month>\\d{2})").matcher("2026-10");
if (m.find()) m.group("year"); // "2026"
"2026-10".replaceAll("(?<y>\\d{4})-(?<m>\\d{2})", "${m}/${y}") // "10/2026"
Two smaller traps. Java group names must start with a letter and may contain only ASCII letters and digits, so (?<order_id>…) is a syntax error in Java but fine in JavaScript and Python. And JavaScript (ES2025) lets one name appear in different alternatives, as in (?<y>\d{4})-\d\d|\d\d-(?<y>\d{4}); Python and Java both reject the duplicate.
Lookbehind
All three support (?<=…) and (?<!…). They disagree on what can go inside.
- JavaScript accepts any pattern, including unbounded ones like
(?<=^\w+:)\d+. Lookbehind is part of ES2018, but Safari only added it in 16.4, which matters if your page has to work on older iPhones. - Python requires a fixed width.
(?<=\$|USD )\d+fails withlook-behind requires fixed-width pattern, because the alternatives are one and four characters long. Alternatives of equal length are fine, and unequal ones can be split into separate lookbehinds:(?:(?<=\$)|(?<=USD ))\d+. - Java requires a computable maximum length.
(?<=\$\d{1,3})and(?<=\$|USD )both work. A repeated group such as(?<=(ab)+)fails with "Look-behind group does not have an obvious maximum length". JDK 21 also accepts+or*on a single character class, like(?<=\d+), but explicit bounds are the safer habit.
When a lookbehind won't port, capture instead. (?:\$|USD )(\d+) works in all three engines; read group 1.
Flags
JavaScript flags follow the literal or go in the second argument of new RegExp: g (all matches), i, m, s (dot matches newline), u (Unicode mode), v (Unicode sets, ES2024), y (sticky: match only at lastIndex) and d (match indices). There's no x flag for comments; new RegExp("a b", "x") throws. A global (?i) at the start of a pattern is a syntax error, but ES2025 added scoped modifiers like (?i:abc); current V8 accepts them, so check your other target browsers before relying on them. The tester has toggles for the five you'll use most: g, i, m, s and u.
Python has no global flag; you pick a function instead. re.search finds the first match, re.findall and re.finditer find all of them. The other flags are constants combined with |: re.I, re.M, re.S, re.X (verbose: whitespace ignored, # starts a comment) and re.A (ASCII-only classes). An inline (?i) must sit at the very start of the pattern; since Python 3.11, putting it anywhere else is an error. The scoped form (?i:…) works anywhere.
Watch re.findall on a pattern with groups: it returns the groups, not the whole matches. re.findall(r"(\w+)=(\d+)", "a=1 b=2") gives [('a', '1'), ('b', '2')].
Java takes a bit mask in Pattern.compile: Pattern.CASE_INSENSITIVE, MULTILINE, DOTALL, COMMENTS, UNICODE_CASE and UNICODE_CHARACTER_CLASS, among others. Inline flags work anywhere and apply from that point on, so ab(?i)c matches abC.
Escaping: the string literal goes first
Before the regex engine sees your pattern, the language's string parser does, and each language fails differently when you forget:
// JavaScript: a regex literal needs no extra escaping
/\d+/
new RegExp("\\d+") // in a string, write \\ for each \
new RegExp("\d+") // silently becomes /d+/: matches "ddd", not "42"
new RegExp(String.raw`\d+`) // String.raw keeps backslashes as typed
# Python: use raw strings
re.search(r"\bcat\b", "a cat here") # matches "cat"
re.search("\bcat\b", "a cat here") # None: "\b" here is a backspace character
// Java: no raw strings, so double every backslash
Pattern.compile("\\d+");
Pattern.compile("\d+"); // compile error: illegal escape character
Pattern.compile("\bcat\b"); // compiles, but \b is a backspace and never matches
Python lets "\d" through unchanged because it isn't a recognised string escape, so it works by accident; since 3.12 it at least emits a SyntaxWarning. Java text blocks don't help, because they process escapes like ordinary strings. To match a literal piece of text inside a pattern, use re.escape() in Python and Pattern.quote() in Java.
Moving a pattern from the tester into a Java or JavaScript string by hand is where backslashes go missing. The string escape tool in Java/C mode doubles every backslash for you.
match, search and fullmatch
The method you call decides how much of the string has to match, and the names don't line up across languages:
anywhere at the start whole string
JavaScript re.test(s) ^ in the pattern ^(?:…)$ in the pattern
Python re.search() re.match() re.fullmatch()
Java matcher.find() matcher.lookingAt() matcher.matches()
Two of these cause most of the bugs. Python's re.match sounds like "match the whole thing" but only anchors the start, so re.match(r"\d+", "42a") succeeds. Java's String.matches() goes the other way and requires the whole string, so "hotdog".matches("cat|dog") is false even though find() succeeds.
Then there's the trailing newline. In Python and Java, $ matches at the end of the string and just before a final \n, so ^\d+$ accepts "42\n". In JavaScript it doesn't. When you validate input that may carry a newline, such as a line read from a file, use re.fullmatch() or matcher.matches(), or anchor the end with \Z in Python and \z in Java. Python 3.14 also accepts \z.
Unicode: what \d, \w and the dot mean
\b\w+\b on "café bar"
JavaScript, with or without u caf, bar
Python 3 café, bar
Java 21, default flags caf, bar
Java 21 with (?U) café, bar
- JavaScript:
\d,\wand\bare ASCII-only, even withu. For letters in any script, use\p{L}, which needs theuorvflag. Withoututhe engine works on UTF-16 code units, so^.$fails on a single character outside the Basic Multilingual Plane, which includes most emoji. - Python 3:
strpatterns are Unicode-aware by default.\dmatches the Arabic-Indic digit٢as well as2, which may not be what a "digits only" field expects. Usere.ASCIIor an explicit[0-9]to restrict it. The standardremodule has no\p{…}classes; the third-partyregexpackage is a separate engine that adds them. - Java:
\dand\ware ASCII by default, andUNICODE_CHARACTER_CLASSor inline(?U)makes them Unicode-aware.\p{L}works without any flag.CASE_INSENSITIVEon its own only folds ASCII, soédoesn't matchÉuntil you addUNICODE_CASE.
Next steps
Build and debug the pattern in the regex tester, then run it past the table at the top before it moves to Python or Java. The language notes at the bottom of the regex cheat sheet are a quick reminder, and the capture groups guide explains group numbering and replacements in detail.