Search and replace
Problem
The bank statement
Your bank sends you text with the payments for the month. You want each amount separately, so you can add them up:
String statement = "Rent €450.00 on 01.10, power €87.35 on 05.10, water €23.10 on 12.10";System.out.println(statement.matches(".*€\\d+\\.\\d{2}.*"));truematches only says "yes, there is an amount somewhere". The .* on both sides is there because matches wants the pattern to cover the whole text. replaceAll would change the amounts, but it does not give them to you. You need a way to find each match and take it.The idea
Pattern and Matcher
For searching there are two classes from java.util.regex:
String statement = "Rent €450.00 on 01.10, power €87.35 on 05.10, water €23.10 on 12.10";Pattern amount = Pattern.compile("€\\d+\\.\\d{2}");Matcher m = amount.matcher(statement);while (m.find()) { System.out.println(m.group());}€450.00€87.35€23.10Pattern.compile(...)turns the pattern into aPatternobject. Do it once and use it as many times as you like.amount.matcher(text)gives aMatcher: a searcher for this pattern in this text.m.find()finds the next match. It returnstrueif there is one, andfalsewhen they run out. That is why it is in awhile.m.group()is the text of the match it just found.
At the top of the file you need import java.util.regex.Matcher; and import java.util.regex.Pattern;.
More precisely, if you are curious
String.matches,replaceAllandsplitactually callPattern.compileevery time. In a loop over thousands of lines it is faster to compile the pattern once, for example in astatic final Patternfield.m.matches()checks the whole text, likeString.matches.m.lookingAt()checks only whether the text starts with a match.
See it
Where the match is
Besides the text, the Matcher also knows where it was found:
String log = "ERROR disk; INFO start; ERROR network";Matcher m = Pattern.compile("ERROR").matcher(log);int count = 0;while (m.find()) { count++; System.out.println(m.group() + " from " + m.start() + " to " + m.end());}System.out.println("Total: " + count);ERROR from 0 to 5ERROR from 24 to 29Total: 2start() is the index of the first character of the match, and end() is the index after the last one, as with substring. That is why log.substring(m.start(), m.end()) is the same as m.group().Try it
A digit or a number?
What will the program print?
Matcher m = Pattern.compile("\\d+").matcher("Table 12, 4 people, 2 bottles");while (m.find()) { System.out.print("[" + m.group() + "]");}System.out.println();\d+ is "one or more digits", so "12" is one match. With \d and no +, each digit would be separate: [1][2][4][2]. The space and the comma break the run, so 12 and 4 do not merge.
The idea
Groups: parts of the match
Parentheses in the pattern make a group. The Matcher remembers what matched each group separately:
Pattern date = Pattern.compile("(\\d{2})\\.(\\d{2})\\.(\\d{4})");Matcher m = date.matcher("Due: 15.11.2026");if (m.find()) { System.out.println(m.group()); System.out.println(m.group(1) + " / " + m.group(2) + " / " + m.group(3)); System.out.println(m.group(3) + "-" + m.group(2) + "-" + m.group(1));}15.11.202615 / 11 / 20262026-11-15- Groups are numbered by their opening parentheses, from left to right, starting at 1.
group(0)orgroup()is the whole match.if (m.find())is for when you need only the first match.
This way you take the day, the month and the year of a date separately, and arrange them the way you need.
Pattern date = Pattern.compile("(\\d{2})\\.(\\d{2})\\.(\\d{4})");Matcher m = date.matcher("Due: 15.11.2026");if (m.find()) { System.out.println(m.group()); System.out.println(m.group(1) + " / " + m.group(2) + " / " + m.group(3)); System.out.println(m.group(3) + "-" + m.group(2) + "-" + m.group(1));}Step by step, in words
- The date pattern has three pairs of parentheses: three groups.
- m is a searcher for this pattern in this text.
- find() finds the date: from index 5 to 15.
- Each group remembers its part: group 1 is 15, group 2 is 11, group 3 is 2026.
- The parts sit in m, each under its own number. group(0) is the whole match.
- group() is the same as group(0): 15.11.2026.
- The three groups, one by one.
- In reverse order, the parts give 2026-11-15.
More precisely, if you are curious
(?:...)is a group that only holds the parts together, without remembering them. For example,(?:\+359|0)8[789]\d{7}does not make a group 1.
m.groupCount()gives the number of groups in the pattern.- If a group did not take part in the match (for example,
(\.\d{2})?was not there),m.group(1)isnull.
The idea
Named groups and replacing
(?<name>...), and then m.group("name"). Here is a line from a café bill:Pattern item = Pattern.compile("(?<qty>\\d+) x (?<name>[A-Za-z ]+) at (?<price>\\d+\\.\\d{2})");Matcher m = item.matcher("2 x Coffee with milk at 3.20; 1 x Croissant at 2.90");while (m.find()) { int qty = Integer.parseInt(m.group("qty")); double price = Double.parseDouble(m.group("price")); System.out.println(m.group("name") + ": €" + qty * price);}Coffee with milk: €6.4Croissant: €2.9replaceAll too: in the replacement text, $1 is group 1, and ${name} is the group with that name. Here are the dates, from the Bulgarian form to ISO and to American:String text = "Deadline: 15.11.2026, reminder: 08.11.2026";System.out.println(text.replaceAll("(\\d{2})\\.(\\d{2})\\.(\\d{4})", "$3-$2-$1"));System.out.println(text.replaceAll("(?<d>\\d{2})\\.(?<m>\\d{2})\\.(?<y>\\d{4})", "${m}/${d}/${y}"));Deadline: 2026-11-15, reminder: 2026-11-08Deadline: 11/15/2026, reminder: 11/08/2026Pattern and Matcher: String.replaceAll understands groups by itself.More precisely, if you are curious
$in the replacement text is special. If you want a real dollar sign, write\\$, or pass the text throughMatcher.quoteReplacement(...)if it comes from the user.
- You can also use a group in the pattern itself:
\1means "the same as group 1". This is how you catch repeated words. See the card on flags.
Try it
How much does + take?
You want all the HTML tags from the line:
What will the program print?
String html = "<b>Price:</b> <i>€12</i>";Matcher m = Pattern.compile("<.+>").matcher(html);while (m.find()) { System.out.println(m.group());}One match, the whole line. .+ takes as much as it can: it goes to the end of the text, then gives back character by character until it finds a >. The first > from the back is the last one in the line. Quantifiers like this are called greedy.
The idea
Greedy and lazy
A ? after a quantifier makes it lazy: it takes as little as it can and stops at the first chance.
String html = "<b>Price:</b> <i>€12</i>";Matcher m = Pattern.compile("<.+?>").matcher(html);while (m.find()) { System.out.println(m.group());}System.out.println(html.replaceAll("<[^>]+>", ""));<b></b><i></i>Price: €12<.+?>is "<, at least one character, and the first>after it".<[^>]+>does the same in another way: "<, characters that are not>, and>". It cannot skip past a>, so it is also faster. With it you remove the tags, and only the text is left.
| Greedy | Lazy |
|---|---|
* |
*? |
+ |
+? |
? |
?? |
{2,5} |
{2,5}? |
Try it
Find the bug
The program should print the three order numbers, but it prints only #1042. Which line has the bug?
Pick the line with the bug.
if calls find() once and finds only the first match. For all of them you need while (m.find()): each call carries on from where the previous one stopped.The idea
Flags and lines
You give the flags as the second argument of Pattern.compile, joined with |. From a log file with several lines, you take the error messages, written in upper or lower case:
String log = "INFO: Start\nERROR: No connection to the database\nINFO: Retrying\nerror: Disk 95%";Pattern p = Pattern.compile("^error: (.+)$", Pattern.CASE_INSENSITIVE | Pattern.MULTILINE);Matcher m = p.matcher(log);while (m.find()) { System.out.println(m.group(1));}No connection to the databaseDisk 95%Pattern.CASE_INSENSITIVEis the same as(?i): upper and lower case letters are the same.Pattern.MULTILINEmakes^and$the start and end of each line. Without it, they are only the start and end of the whole text, and the pattern will find nothing.Pattern.DOTALLlets.catch a new line too.
More precisely, if you are curious
Two things you will run into in other people's patterns:
- A backreference
\1: "the same text as group 1". This is how you catch repeated words. - A lookahead
(?=...): "somewhere from here on there is ...", without taking any characters. Several of them in a row are handy for passwords: "has a digit" and "has a capital letter", and at least 8 characters.
Pattern twice = Pattern.compile("(?U)\\b(\\w+) \\1\\b");Matcher m = twice.matcher("Meet me at the café café, please please be on time.");while (m.find()) { System.out.println("Repeated: " + m.group(1));}String strong = "(?=.*\\d)(?=.*[A-Z]).{8,}";System.out.println("secret12".matches(strong) + " " + "Secret12".matches(strong));Repeated: caféRepeated: pleasefalse true(?!...) is the opposite: "from here on there is no ...". There is also lookbehind: (?<=...) and (?<!...).
Exercise
Find, extract, replace
Solve the tasks in your editor. The text is one line from the keyboard. In the lesson "Files" you will do them with a file too.
1. Find and replace. Replace the word "old" with "new" everywhere it is a whole word, and say how many replacements you made. Parts of other words ("oldest") are left alone.
This is old content. More old content, not the oldest.This is new content. More new content, not the oldest.Replaced: 22. Words with a prefix. The first line is the prefix, the second is the text. Print each word that starts with the prefix.
appapple application banana appreciate snappleappleapplicationappreciate3. Addresses in text. Extract all web addresses that start with http:// or https://. The dot at the end of a sentence is not part of the address.
See the site https://www.example.com and this one: http://www.test.com. The documents are at https://docs.example.com/guide/start.https://www.example.comhttp://www.test.comhttps://docs.example.com/guide/start4. Dates to ISO. Turn every date dd.mm.yyyy in the text into the form yyyy-mm-dd with one replaceAll.
Contract of 03.10.2026, payment by 15.10.2026.Contract of 2026-10-03, payment by 2026-10-15.More tasks
- Statement total. Take the amounts from the first card with a group for the euros and a group for the cents, add them up and print "Total: €560.45".
- Hashtags. Extract all words that start with
#from a post that mixes plain Latin letters and letters like "é". Think about why you need(?U). - No tags. Remove the HTML tags from
<p>The deadline is <b>15.11</b>.</p>and print the clean text. - Phone numbers in text. Find all mobile numbers in a text and print them in the form
+359 88 812 3456, with groups andreplaceAll.
Test