Lesson 52Phase 5: Errors, files and the standard library
Regular expressions: patterns in text
Describing the shape of text with a pattern, then searching, validating, splitting and replacing with Pattern and Matcher.
By BasithUpdated 50 min
- regex
- Pattern
- Matcher
- character class
- quantifier
- group
A sign-up form wants a username of 3 to 15 small letters, digits or underscores. A delivery form wants a postcode shaped like SW1A 1AA, and a phone field wants a zero, four digits, a space and six more. A shop wants every price pulled out of a paragraph of text. Each of these jobs is about the shape of some text: does it have the right shape, and where in a longer text does that shape appear? A regular expression is a short pattern that describes a shape like that, and Java can use one to check text, search it, split it and change it.
- Read and write patterns made of character classes, shorthands such as
\d, the dot, quantifiers, anchors, alternation and groups. - Put a regex into a Java string, writing every backslash twice.
- Check a whole string with
matches, and search inside a text with aPattern, aMatcherand afind()loop. - Pull pieces out of each match with numbered and named groups.
- Split and replace text with a regex, and avoid the traps of
split(".")and a greedy.*. - Compile a pattern once, use flags and
Pattern.quote, and read aPatternSyntaxException.
length, charAt, indexOf, substring and replace. You have also used split(" ") once, to cut a sentence into words in HashMap and HashSet. The backslash escapes such as \n come from variables and types, the import lines from packages and imports, and catching an exception from exception handling. One exercise adds up prices with BigDecimal.Checking the shape of text#
Start with the username rule: 3 to 15 characters, each one a small letter, a digit or an underscore. With the String methods you already know, the check is a length test and a loop over the characters.
public class Main { public static void main(String[] args) { System.out.println(isValid("sana_99")); System.out.println(isValid("Sana!")); System.out.println(isValid("al")); } static boolean isValid(String name) { if (name.length() < 3 || name.length() > 15) { return false; } for (int i = 0; i < name.length(); i++) { char c = name.charAt(i); boolean allowed = (c >= 'a' && c <= 'z') || (c >= '0' && c <= '9') || c == '_'; if (!allowed) { return false; } } return true; }}It prints true, false and false. “Sana!” breaks the rule twice, with a capital S and an exclamation mark, and “al” is too short. The method works, but it takes a dozen lines to say one short rule, and the rule is hard to see among them. A postcode would need a longer method still, with different checks for different parts of the text.
Here is the same check written with a regular expression.
public class Main { public static void main(String[] args) { String rule = "[a-z0-9_]{3,15}"; System.out.println("sana_99".matches(rule)); System.out.println("Sana!".matches(rule)); System.out.println("al".matches(rule)); }}It prints the same three answers, and the whole rule is now the string [a-z0-9_]{3,15}. A regular expression, or regex for short, is a piece of text that describes a pattern of characters. This one has two parts. [a-z0-9_]means “one character that is a small letter, a digit or an underscore”, and {3,15}means “3 to 15 of those in a row”. The String method matches returns true when the whole string fits the pattern.
The pattern language has only a handful of pieces. The next sections add them one at a time, each with a program to run, and by the end of the page you can read every character of patterns like this one.
remodule and JavaScript's regexes use almost the same pattern language, so most of what you know carries over. Two things differ. Java has no regex literal like JavaScript's /\d+/, so a pattern always lives in an ordinary string, and that matters for backslashes, as you will see. And Java's matcheschecks the whole string, like Python's re.fullmatch. Python's re.matchonly checks the start, and JavaScript's test searches anywhere in the text.Character classes, shorthands and the dot#
Most characters in a pattern stand for themselves. The pattern catmatches the text “cat” and nothing else: a c, then an a, then a t. Patterns get useful when one position may hold one of several characters.
A character class is a list of characters inside square brackets. It matches exactly one character, which must be one of those in the list. [bcr]at matches bat, cat and rat. Inside the brackets you can also write:
- A range, such as
[a-z]: any one small letter from a to z.[A-Z]and[0-9]work the same way, and ranges can sit side by side, so[a-zA-Z0-9]is any one letter or digit. - A caret straight after the opening bracket, as in
[^0-9]. This is a negated class: it matches any one character that is missing from the list. Here, anything except a digit.
public class Main { public static void main(String[] args) { System.out.println("cat".matches("cat")); System.out.println("cat".matches("[bcr]at")); System.out.println("hat".matches("[bcr]at")); System.out.println("rat".matches("[a-z]at")); System.out.println("Rat".matches("[a-z]at")); System.out.println("7".matches("[^0-9]")); System.out.println("x".matches("[^0-9]")); }}It prints true, true, false, true, false, false, true. “hat” fails because h is not in [bcr]. “Rat” fails because [a-z]holds small letters only, and to Java a capital R is a different character. “7” fails [^0-9]because it is a digit, and “x” passes because it is not.
[a-z] is one character with 26 choices, and [bcr]at always matches exactly three characters.[A-Z][0-9] when checked with matches?Shorthand classes and the dot
A few classes come up so often that they have short names, each written as a backslash and a letter.
| Shorthand | Matches one | Same as |
|---|---|---|
\d | digit | [0-9] |
\w | word character: a letter, a digit or an underscore | [a-zA-Z_0-9] |
\s | whitespace character: a space, a tab, a line break or one of a few rarer ones | [ \t\n\x0B\f\r] |
\D | character that is not a digit | [^0-9] |
\W | character that is not a word character | [^\w] |
\S | character that is not whitespace | [^\s] |
. | character of any kind except a line break | no class needed |
The dot is the widest of them. c.tmatches cat, cot, c7t and even “c t” with a space in the middle.
The double backslash
Now try \d in a program. The obvious line does not even compile:
$ javac Main.javaMain.java:3: error: illegal escape character System.out.println("123".matches("\d+")); ^1 errorInside a Java string, a backslash starts an escape sequence such as \n for a line break. \dis not one of Java's escape sequences, so the compiler refuses it. To put one real backslash into a string, you write two. The string "\\d" holds two characters, a backslash and a d, and that pair is what the regex engine reads as \d.
public class Main { public static void main(String[] args) { String threeDigits = "\\d\\d\\d"; System.out.println(threeDigits); System.out.println("123".matches(threeDigits)); System.out.println("12a".matches(threeDigits)); System.out.println("cat".matches("c.t")); System.out.println("c t".matches("c.t")); System.out.println("c.t".matches("c\\.t")); System.out.println("cat".matches("c\\.t")); }}The first line prints \d\d\d: the string holds three backslashes, though the source code shows six. Then true, and falsefor “12a”, which ends in a letter. The last four lines are about the dot. c.taccepts both “cat” and “c t”. To match a real dot, put a backslash in front of it: the regex c\.t, written "c\\.t"in Java, matches “c.t” and rejects “cat”.
\d"\\d"\d{3}-\d{4}"\\d{3}-\\d{4}"\. (a real dot)"\\."\\ (a real backslash)"\\\\"The characters with a job in a pattern are . * + ? ^ $ | ( ) [ ] { } and the backslash itself. To match one of them as a plain character, put a backslash in front: \$ matches a dollar sign and \( an opening bracket. Java accepts a backslash in front of any punctuation mark, needed or not, so when in doubt you can escape it. A backslash in front of a letter is different: those pairs are reserved for shorthands such as \d, and an unknown one such as \y is an error.
Key ideaA regex goes into Java as an ordinary string, so every backslash in the pattern is written twice. The regex
\d+is the Java string"\\d+".
\d{3}, three digits, in a Java program. Which line is right?Saying how many: quantifiers#
So far every piece of a pattern has matched exactly one character. A quantifier goes straight after a piece and says how many times that piece may repeat.
| Quantifier | How many | Example | Matches |
|---|---|---|---|
* | zero or more | ab*c | ac, abc, abbbbc |
+ | one or more | ab+c | abc, abbbbc, but not ac |
? | zero or one, so the piece is optional | colou?r | color, colour |
{n} | exactly n | \d{4} | 2026 |
{n,} | n or more | \d{2,} | 42, 1234567 |
{n,m} | from n to m | [a-z]{3,15} | sana, banana |
public class Main { public static void main(String[] args) { System.out.println("ac".matches("ab*c")); System.out.println("ac".matches("ab+c")); System.out.println("abbbc".matches("ab+c")); System.out.println("color".matches("colou?r")); System.out.println("colour".matches("colou?r")); System.out.println("2026".matches("\\d{4}")); System.out.println("202".matches("\\d{4}")); System.out.println("x".matches("[a-z]{3,15}")); }}It prints true, false, true, true, true, true, false, false. ab*caccepts “ac” because zero b's is allowed, while ab+c needs at least one. “202” is one digit short for \d{4}, and a lone “x” is too short for [a-z]{3,15}.
ab+means an a followed by one or more b's, so it matches “abbb” and never “abab”. To repeat several characters together, wrap them in parentheses: (ab)+matches “ababab”. Parentheses have a second job too, which the section on groups explains.Reading a real pattern
You now have enough pieces for the phone and postcode checks from the start of the page. A phone number shaped like 07700 900123 is a zero, four more digits, a space and six digits: 0\d{4} \d{6}. UK postcodes come in more shapes (SW1A 1AA, M1 1AE, B33 8TH), and a common pattern for them is [A-Z]{1,2}\d[A-Z\d]? \d[A-Z]{2}. Read it from left to right:
- 1
[A-Z]{1,2}: one or two capital letters (SW). - 2
\d: a digit (1). - 3
[A-Z\d]?: maybe one more capital letter or digit (A). A shorthand can go inside square brackets. - 4A space.
- 5
\d: a digit (1). - 6
[A-Z]{2}: two capital letters (AA).
public class Main { public static void main(String[] args) { String phone = "0\\d{4} \\d{6}"; String postcode = "[A-Z]{1,2}\\d[A-Z\\d]? \\d[A-Z]{2}"; System.out.println("07700 900123".matches(phone)); System.out.println("7700 900123".matches(phone)); System.out.println("SW1A 1AA".matches(postcode)); System.out.println("M1 1AE".matches(postcode)); System.out.println("SW1A1AA".matches(postcode)); }}It prints true, false, true, true, false. The second phone number has lost its leading zero. “M1 1AE” fits because the optional piece can be absent, and “SW1A1AA” fails because the pattern asks for a space.
ABC-1234: three capital letters, a hyphen and four digits. Write the pattern so that the program prints true, false, false, true.Finding matches inside text#
matchesanswers one question about a whole string. The shop's other job is different. A menu says “Tea is 3.50, cake is 4.25 and a pot for two is 6.00.”, and you want every price in it. The prices sit somewhere inside the text, and there could be any number of them. For searching, Java has two classes in the package java.util.regex:
Patternholds a regex that Java has compiled: checked for mistakes and turned into a form it can run quickly. You make one withPattern.compile(regex).Matcheris a pattern at work on one particular text. You get one from the pattern withpattern.matcher(text).
A matcher's find() method searches for the next stretch of the text that fits the pattern, called a match. It returns true when it finds one, and then three methods describe it: group() returns the matched text, start() the index of its first character, and end() the index just after its last character, which is how substring counts too.
import java.util.regex.Matcher;import java.util.regex.Pattern; public class Main { public static void main(String[] args) { String menu = "Tea is 3.50, cake is 4.25 and a pot for two is 6.00."; Pattern price = Pattern.compile("\\d+\\.\\d{2}"); Matcher m = price.matcher(menu); while (m.find()) { System.out.println(m.group() + " at " + m.start() + " to " + m.end()); } }}Step through it and read the narration for each find(): it names the text it found and where. The program prints 3.50 at 7 to 11, 4.25 at 21 to 25 and 6.00 at 47 to 51. Each find() carries on from where the last match ended, and once no price is left it returns false and the loop stops. The full stop after 6.00 stays out of the last match, because after \. the pattern asks for exactly two digits.
Key idea
matchesasks whether the whole text fits the pattern.find()looks for the next part of the text that fits, and a loop offind()calls visits every match in turn.
group(), start() and end() describe the last successful match. Call them before any find(), or after a find() that returned false, and Java throws an IllegalStateException with the message No match found.total and prints Total: 14.60. new BigDecimal(m.group()) turns a matched price such as “4.50” into an exact amount.matches or find?
import java.util.regex.Matcher;import java.util.regex.Pattern; public class Main { public static void main(String[] args) { System.out.println("abc123".matches("[a-z]+")); Matcher m = Pattern.compile("[a-z]+").matcher("abc123"); System.out.println(m.find()); System.out.println(m.group()); }}[a-z]+ means one or more small letters. What do the three lines print?
It prints false, true and abc. matchesneeds the pattern to cover all of “abc123”, and [a-z]+ has no way to cover the digits. find()only needs some part of the text to fit, and the first part that does is “abc”.
text.matches(regex). On a matcher, m.matches().m.find().Anchors: ^ and $
Sometimes it matters where in the text a match sits. ^ matches at the very start of the text, and $ at the very end. They are called anchors, and they match a position between characters, never a character itself. So ^\d+ finds digits only when they open the text.
import java.util.regex.Pattern; public class Main { public static void main(String[] args) { Pattern atStart = Pattern.compile("^\\d+"); Pattern atEnd = Pattern.compile("\\d+$"); String[] lines = {"42 apples", "apples 42", "42"}; for (String line : lines) { boolean starts = atStart.matcher(line).find(); boolean ends = atEnd.matcher(line).find(); System.out.println(line + ": starts " + starts + ", ends " + ends); } }}It prints 42 apples: starts true, ends false, then apples 42: starts false, ends true, then 42: starts true, ends true. With matches, anchors add nothing, because matches already needs the whole text to fit. ^\d+$ with find() asks the same question as \d+ with matches.
^[^0-9] finds a first character that is not a digit.Try your own patterns
The tester below runs your pattern over your text with Java's own matching rules, the same ones the Run button uses. It marks every match a find() loop would report, lists each one with its start and end, and shows the pattern as you would write it in Java, every backslash doubled. Start from the examples, then change one character at a time: take the \. out of the price pattern, change {2} to {1,2}, or put a ^ at the front.
In Java: Pattern.compile("\\d+\\.\\d{2}")
52 of 300 characters. Patterns can be up to 100.
Every match a find() loop reports
- 1"3.50" start 7, end 11
- 2"4.25" start 21, end 25
- 3"6.00" start 47, end 51
- matches() on the whole text
- false
- groupCount()
- 0
Alternation and groups#
Either one or the other
A vertical bar means “or”. This is called alternation: cat|dog matches cat or dog. The bar splits the whole pattern into two halves, so I like cats|dogs means “I like cats” or “dogs”. To limit the choice to part of the pattern, put that part in parentheses.
public class Main { public static void main(String[] args) { System.out.println("dog".matches("cat|dog")); System.out.println("I like dogs".matches("I like cats|dogs")); System.out.println("I like dogs".matches("I like (cats|dogs)")); System.out.println("grey".matches("gr(a|e)y")); }}It prints true, false, true, true. The second line is the trap: the bar took everything on its left as one choice, so that pattern accepts only “I like cats” or a bare “dogs”. With brackets, (cats|dogs) offers a choice for the last word only, and gr(a|e)y accepts both spellings of grey.
Capturing groups
Parentheses make a group, and a group has a second job: it captures, which means the matcher remembers the text that part of the pattern matched. Groups are numbered by counting opening brackets from the left, starting at 1, and m.group(1) returns what group 1 captured. Group 0 is the whole match, so m.group(0) is the same as m.group(). m.groupCount() says how many groups the pattern has.
import java.util.regex.Matcher;import java.util.regex.Pattern; public class Main { public static void main(String[] args) { Pattern date = Pattern.compile("(\\d{4})-(\\d{2})-(\\d{2})"); Matcher m = date.matcher("Ordered 2026-10-18, delivered 2026-11-02."); while (m.find()) { String year = m.group(1); String month = m.group(2); String day = m.group(3); System.out.println(m.group() + " is day " + day + ", month " + month + ", year " + year); } System.out.println("Groups in the pattern: " + m.groupCount()); }}It prints 2026-10-18 is day 18, month 10, year 2026, then 2026-11-02 is day 02, month 11, year 2026, then Groups in the pattern: 3. Watch year, month and day fill up as you step: the three groups take each date apart, and the program can use the parts in any order it likes.
((\d{2}):(\d{2})) finds a match in “at 09:45”. What does m.group(2) return?Named groups
Numbers are easy to mix up once a pattern has several groups. You can name a group instead, by writing (?<name>...), and read it back with m.group("name").
import java.util.regex.Matcher;import java.util.regex.Pattern; public class Main { public static void main(String[] args) { Pattern date = Pattern.compile("(?<year>\\d{4})-(?<month>\\d{2})-(?<day>\\d{2})"); Matcher m = date.matcher("2026-10-18"); if (m.matches()) { System.out.println("Year: " + m.group("year")); System.out.println("Month: " + m.group("month")); System.out.println("Day: " + m.group("day")); System.out.println("Group 1 is still " + m.group(1)); } }}It prints Year: 2026, Month: 10, Day: 18 and Group 1 is still 2026: a named group keeps its number too. This program calls the matcher's own matches(), which checks the whole text like the String method does, and once it returns true the groups can be read.
(?<first_name>\w+) throws a PatternSyntaxException. Write firstName instead.Ada scored 91, Grace scored 88 and Alan scored 79, one per line.Tap a line, then tap where it should go. Or use the arrows.
Splitting text#
You have used split to cut a sentence at its spaces. Its argument was a regex all along, which makes it far more flexible than it looked. To see the pieces, these programs print each array with Arrays.toString, from java.util.Arrays, which lists an array's items between square brackets.
import java.util.Arrays; public class Main { public static void main(String[] args) { String messy = "one two three"; System.out.println(Arrays.toString(messy.split(" "))); System.out.println(Arrays.toString(messy.split("\\s+"))); String fruit = "apples, pears ,plums; figs"; System.out.println(Arrays.toString(fruit.split("\\s*[,;]\\s*"))); }}Splitting at a single space leaves an empty string wherever two spaces sat side by side: [one, , two, , , three]. Splitting at \s+, one or more whitespace characters, treats each run of spaces as one cut and gives [one, two, three]. The last line cuts at a comma or a semicolon together with any spaces around it, \s*[,;]\s*, and prints [apples, pears, plums, figs].
Three traps
Because the argument is a regex, a character with a job in patterns does that job, whatever the text looks like. Decide what this prints before you run it.
public class Main { public static void main(String[] args) { String version = "21.0.2"; String[] parts = version.split("."); System.out.println(parts.length); }}A program splits a version number at its dots. What does it print?
It prints 0, the length of an empty array. The dot means any character, so every character of “21.0.2” is a cut, and every piece between two cuts is an empty string. split then drops the empty strings at the end of the result, and here that is all of them.
import java.util.Arrays; public class Main { public static void main(String[] args) { System.out.println(Arrays.toString("21.0.2".split("."))); System.out.println(Arrays.toString("21.0.2".split("\\."))); System.out.println(Arrays.toString("red|green|blue".split("|"))); System.out.println(Arrays.toString("red|green|blue".split("\\|"))); System.out.println(Arrays.toString("Ada,91,,".split(","))); System.out.println(Arrays.toString("Ada,91,,".split(",", -1))); }}Read the output in pairs, each trap followed by its fix:
split(".")gives[]. Escape the dot,split("\\."), to get[21, 0, 2].split("[.]")works too, because inside square brackets a dot is a plain dot.split("|")cuts between every pair of characters. The bar means “or”, here with an empty pattern on each side, and an empty pattern fits at every position in the text, so every position becomes a cut.split("\\|")cuts at the real bars.- The spreadsheet row
Ada,91,,has two empty cells at the end, andsplit(",")drops them:[Ada, 91]. Pass a negative limit,split(",", -1), to keep every piece:[Ada, 91, , ]. A cut at the very start is different: it does leave an empty first piece, so",a,b".split(",")gives[, a, b].
Key idea
split,replaceAll,replaceFirstandmatchesall read their first argument as a regex. A dot, a bar or a bracket in it does its regex job unless you escape it.
Replacing text#
replaceAll(regex, replacement) returns a copy of the string with every match of the regex replaced, and replaceFirst replaces only the first match. Both take a regex. The replace method from the strings lesson looks for plain text instead.
public class Main { public static void main(String[] args) { String phone = "077 00-900 123"; System.out.println(phone.replaceAll("\\D", "")); String spaced = "too many spaces"; System.out.println(spaced.replaceAll("\\s+", " ")); System.out.println("a.b.c".replace(".", "-")); System.out.println("a.b.c".replaceAll(".", "-")); System.out.println("a.b.c".replaceFirst("\\.", "-")); }}\D matches every character that is not a digit, and replacing each one with nothing leaves only the digits: 07700900123. \s+ squeezes every run of spaces into a single space. The last three lines show what the regex changes. replace swaps the real dots and prints a-b-c. replaceAll(".", "-") swaps every character, since the dot matches anything, and prints -----. replaceFirst with an escaped dot changes only the first dot: a-b.c.
Groups in the replacement
In the replacement text, $1 stands for whatever group 1 captured, $2 for group 2, and so on. That lets you rearrange the parts of every match. Here, dates written year first become day first:
public class Main { public static void main(String[] args) { String log = "Shipped 2026-10-18, delivered 2026-10-21."; String byNumber = log.replaceAll("(\\d{4})-(\\d{2})-(\\d{2})", "$3/$2/$1"); System.out.println(byNumber); String byName = log.replaceAll("(?<y>\\d{4})-(?<m>\\d{2})-(?<d>\\d{2})", "${d}.${m}.${y}"); System.out.println(byName); }}It prints Shipped 18/10/2026, delivered 21/10/2026. The replacement $3/$2/$1 puts group 3, the day, first. A named group goes into a replacement as ${name}, and the second line prints Shipped 18.10.2026, delivered 21.10.2026.
$ starts a group reference, "Price: 5".replaceAll("\\d+", "$10") does not put in $10. Java reads $1 as group 1, finds no such group and throws IndexOutOfBoundsException: No group 1. For a real dollar sign, write "\\$10" in the Java string. For replacement text that you did not write yourself, such as text a user typed, wrap it in Matcher.quoteReplacement(...), which makes every $ and backslash in it plain.Greedy and reluctant
Here is a common first try at removing HTML tags from a bit of text. A tag starts with < and ends with >, so <.*> looks as if it matches one tag.
public class Main { public static void main(String[] args) { String html = "Hello <b>world</b>!"; System.out.println(html.replaceAll("<.*>", "")); }}What does this print?
It prints Hello !, and the word world goes with the tags. Quantifiers are greedy: .* first takes as many characters as it can, all the way to the end of the text, then gives them back one at a time until the rest of the pattern can match. The first > it meets while backing up is the last one in the text, so the single match runs from the first < to the last >: <b>world</b>.
Put a ? after a quantifier and it becomes reluctant (some people say lazy). It takes as few characters as it can, and takes one more only when the rest of the pattern fails. <.*?> stops at the first > after each <.
import java.util.regex.Matcher;import java.util.regex.Pattern; public class Main { public static void main(String[] args) { String html = "Hello <b>world</b>!"; Matcher greedy = Pattern.compile("<.*>").matcher(html); while (greedy.find()) { System.out.println("greedy: " + greedy.group()); } Matcher reluctant = Pattern.compile("<.*?>").matcher(html); while (reluctant.find()) { System.out.println("reluctant: " + reluctant.group()); } System.out.println(html.replaceAll("<.*?>", "")); }}Step through it and compare the narration of the two loops. The greedy matcher finds one match, <b>world</b>. The reluctant one finds two, <b> and </b>, and removing them leaves Hello world!. The Greedy and Reluctant examples in the tester show the same difference on a longer text.
Key idea
*,+,?and{n,m}are greedy: they match as much as they can. A?after one, as in.*?, makes it match as little as it can.
<[^>]*> also matches exactly one tag: a <, any characters except >, then a >. It cannot run past the end of a tag, whatever comes after it. Real web pages are harder, with tags inside tags and > signs inside quotes, so programs that read whole pages use an HTML parser library instead of a regex.Patterns in real programs#
Compile once, use many times
String.matches is handy, but every call compiles its regex from scratch. When a program checks many strings against the same pattern, such as every username in a list of sign-ups, compile the pattern once and keep it. A static final field from static and final is the usual place.
import java.util.regex.Pattern; public class Main { static final Pattern USERNAME = Pattern.compile("[a-z0-9_]{3,15}"); public static void main(String[] args) { String[] signups = {"sana_99", "Ravi", "x", "grace_h", "ada.l"}; for (String name : signups) { if (USERNAME.matcher(name).matches()) { System.out.println(name + " is fine"); } else { System.out.println(name + " is not allowed"); } } }}It prints sana_99 is fine, Ravi is not allowed, x is not allowed, grace_h is fine and ada.l is not allowed. The pattern is compiled once, when the class is set up, and each name gets a fresh Matcher of its own.
Flags
A flag changes how a whole pattern matches. You pass flags as a second argument to Pattern.compile. The one you will use most is Pattern.CASE_INSENSITIVE, which lets letters match in either case.
import java.util.regex.Matcher;import java.util.regex.Pattern; public class Main { public static void main(String[] args) { String text = "Java, JAVA and java are the same word here."; System.out.println("Exact case: " + count(Pattern.compile("java"), text)); System.out.println("Any case: " + count(Pattern.compile("java", Pattern.CASE_INSENSITIVE), text)); System.out.println("JaVa".matches("(?i)java")); } static int count(Pattern pattern, String text) { Matcher m = pattern.matcher(text); int found = 0; while (m.find()) { found++; } return found; }}It prints Exact case: 1, Any case: 3 and true. The last line switches the flag on another way: (?i) at the start of the regex itself. That form also works in String.matches, split and replaceAll, which have no place for a flags argument.
| Flag | Inside the regex | What it changes |
|---|---|---|
CASE_INSENSITIVE | (?i) | Letters match in either case. Only the English letters a to z, unless you add UNICODE_CASE as well. |
MULTILINE | (?m) | ^ and $ match at the start and end of every line, as well as of the whole text. |
DOTALL | (?s) | The dot matches line breaks too. |
COMMENTS | (?x) | Spaces in the pattern are ignored and # starts a comment, so a long pattern can be spread out and explained. |
Searching for text with special characters
Say a search box lets people type what to look for. If someone searches for 1+1, using their text as a regex goes wrong, because + is a quantifier. Pattern.quote turns any text into a pattern that matches exactly that text.
import java.util.regex.Matcher;import java.util.regex.Pattern; public class Main { public static void main(String[] args) { String text = "Total: 1+1=2, not 11=2"; String search = "1+1"; Matcher asRegex = Pattern.compile(search).matcher(text); while (asRegex.find()) { System.out.println("as a regex: " + asRegex.group() + " at " + asRegex.start()); } Matcher quoted = Pattern.compile(Pattern.quote(search)).matcher(text); while (quoted.find()) { System.out.println("quoted: " + quoted.group() + " at " + quoted.start()); } System.out.println(Pattern.quote(search)); }}As a regex, 1+1 means one or more 1s followed by another 1, so it finds the 11 at index 18 and misses the real “1+1”: as a regex: 11 at 18. Quoted, it finds quoted: 1+1 at 7. The last line shows how quote works. It wraps the text in \Q and \E, and everything between those two markers counts as plain text.
When the pattern is broken
A regex can contain mistakes, just as code can: a bracket that never closes, a * with nothing before it to repeat, a range written backwards. Java finds them when it compiles the pattern and throws a PatternSyntaxException. It is an unchecked exception, so the compiler does not make you catch it, and every method that takes a regex can throw it, matches, split and replaceAll included.
import java.util.regex.Pattern;import java.util.regex.PatternSyntaxException; public class Main { public static void main(String[] args) { String[] attempts = {"[a-z", "*.txt", "\\d{2,1}", "\\d{1,2}"}; for (String regex : attempts) { try { Pattern.compile(regex); System.out.println(regex + " is a valid pattern"); } catch (PatternSyntaxException e) { System.out.println(e.getMessage()); } System.out.println(); } }}Each message names the problem, gives the index in the pattern where Java gave up, and draws a caret under that spot. The first one, for example, is Unclosed character class near index 3, with the caret under the z. *.txt is a typical slip: it is how you ask a file manager for every text file, but in a regex a quantifier needs something in front of it to repeat. The regex for that job is .*\.txt. The Broken example in the tester shows the same message, and changes as you fix the pattern.
Matcher.results() hands you all the matches at once as a stream, and Pattern.splitAsStream splits a text into one. Streams are the subject of the next lesson, Streams: processing collections step by step.Try it
Answer each one in your head or on paper first, then open the answer.
\d{3}-\d{4}, and give a piece of text it matches."Room 42".matches("\\d+") return false, when find() with the same pattern on the same text returns true?"a1b22c333".split("\\d+") return? Think about the end of the string.(\w+)@(\w+)\.com searches “Write to ada@example.com today”. After one find(), what do m.group(1), m.group(2) and m.group() return?Bob,,. Why does split(",") give one piece, and how do you get all three?Practice
Work through these on your own. Each one builds on this page.
Learning #java today, then #regex and #coffee, one per line. A hashtag is a # followed by one or more word characters.replaceAll.boolean isStrong(String password) that returns true only when the password has at least 8 characters, at least one digit and at least one capital letter. Use three small patterns with find(), one for each rule, instead of one long regex, and test it on five passwords of your own.\1 matches the same text that group 1 just captured.2026-10-18 09:15 ERROR Disk full. Write a pattern with the named groups date, time, level and message, then print the time and message of only the ERROR lines from a few log lines of your own.Further reading#
- The Pattern class in the Java SE 25 API documentation, with the full table of regex syntax and flags
- The Matcher class in the Java SE 25 API documentation, including the rules for $ and backslashes in replacement text
- Introducing regular expressions, the start of the regex tutorial on dev.java
- Quantifiers on dev.java, with greedy, reluctant and possessive forms side by side
Spotted a mistake, or a part that did not make sense? Report it by email