Pattern.compile turns a regular expression into a reusable, thread-safe object; store it in a static final field. In Java strings every backslash is doubled, so the regex \d{8} is written "\\d{8}". matcher.matches() requires the whole string to match (ideal for validation), while matcher.find() moves to the next match anywhere in the text. Named groups (?<score>\d+) are read with m.group("score").
replaceAll rewrites every match using $1-style group references, and since Java 9 it also accepts a function that computes each replacement. split takes a regex, so "\\s*[;,]\\s*" splits on commas or semicolons with any spaces around them. strip, repeat, lines and String.join handle everyday text work without regexes.
Building a string with + inside a loop copies the text every time; StringBuilder appends efficiently. Text blocks (""") write multi-line strings without \n escapes, and formatted(...) fills them in like printf.
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
public class TextTools {
// Compile patterns once and reuse them: Pattern objects are immutable and thread-safe.
private static final Pattern PHONE = Pattern.compile("(?:\\+255|0)([67]\\d{8})");
private static final Pattern RESULT = Pattern.compile("(?<name>[A-Z][a-z]+)(?: scored)? (?<score>\\d+) in (?<subject>\\w+)");
public static void main(String[] args) {
for (String raw : List.of("0712345678", "+255754000111", "0812345678")) {
Matcher m = PHONE.matcher(raw);
System.out.printf("%-15s %s%n", raw, m.matches() ? "valid -> 0" + m.group(1) : "invalid"); // matches() = whole string
}
String text = "Amina scored 88 in Maths, Juma 42 in Maths and Neema 71 in Biology.";
Matcher m = RESULT.matcher(text);
while (m.find()) { // find() = next match anywhere
System.out.println(m.group("name") + " " + m.group("subject") + " " + Integer.parseInt(m.group("score")));
}
// replaceAll with $1 references, or with a function of each match (Java 9+)
System.out.println("0712345678".replaceAll("(\\d{4})(\\d{3})(\\d{3})", "$1 $2 $3"));
System.out.println(Pattern.compile("\\d+").matcher(text)
.replaceAll(r -> Integer.parseInt(r.group()) >= 50 ? r.group() : r.group() + " (fail)"));
// split, join and whitespace clean-up
String[] subjects = "Maths; Biology ,Chemistry;Physics".split("\\s*[;,]\\s*");
System.out.println(String.join(" | ", subjects));
System.out.println(" Juma Said ".strip().replaceAll("\\s+", " "));
// StringBuilder for building text in a loop (String + in a loop copies every time)
StringBuilder csv = new StringBuilder("name,score\n");
for (String line : List.of("Amina,88", "Juma,42")) csv.append(line).append('\n');
System.out.print(csv);
// Text blocks (Java 15+) and formatted()
String letter = """
Dear %s,
Your child scored %d in %s.
""".formatted("Mama Amina", 88, "Maths");
System.out.print(letter);
}
}Key points
- Compile patterns once;
matches()validates the whole string,find()searches. - Named groups,
replaceAll(with$1or a function) and regexsplitcover most parsing. - Use
StringBuilderin loops and text blocks for multi-line text.
Exercise
Parse lines like ADM-2026-0001 : Amina Hassan : 88 into records (admission, year, name, score) with named groups. Reject bad admission numbers, scores above 100 and unreadable lines with a clear message for each, collected in a StringBuilder. Then convert dates like 02/11/2026 in a notice to ISO format with replaceAll.
Show solution
Try the exercise yourself first — then compare your approach with this one.
LINE reads the three parts with named groups and tolerates extra spaces; ADMISSION is checked separately so the error message can say exactly what is wrong. parse throws IllegalArgumentException with a message, and the loop collects the problems in a StringBuilder. A text block holds the sample input, and lines() splits it.
import java.util.ArrayList;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
public class Roster {
record Line(String admission, int year, String name, int score) {}
private static final Pattern LINE = Pattern.compile(
"\\s*(?<adm>\\S+)\\s*:\\s*(?<name>[A-Za-z' ]+?)\\s*:\\s*(?<score>\\d{1,3})\\s*");
private static final Pattern ADMISSION = Pattern.compile("ADM-(\\d{4})-(\\d{4})");
private static final Pattern DMY = Pattern.compile("\\b(\\d{2})/(\\d{2})/(\\d{4})\\b");
// Returns the parsed line, or throws with a message saying exactly what is wrong.
static Line parse(String text) {
Matcher m = LINE.matcher(text);
if (!m.matches()) throw new IllegalArgumentException("cannot read \"" + text.strip() + "\"");
Matcher adm = ADMISSION.matcher(m.group("adm"));
if (!adm.matches()) throw new IllegalArgumentException("bad admission number " + m.group("adm"));
int score = Integer.parseInt(m.group("score"));
if (score > 100) throw new IllegalArgumentException("score " + score + " is above 100");
String name = m.group("name").replaceAll("\\s+", " ");
return new Line(m.group("adm"), Integer.parseInt(adm.group(1)), name, score);
}
public static void main(String[] args) {
String input = """
ADM-2026-0001 : Amina Hassan : 88
ADM-2026-0002: Juma Said:42
ADM-26-0003 : Neema Kimaro : 71
ADM-2025-0104 : Ali Mohamed : 105
just some text
""";
List<Line> ok = new ArrayList<>();
StringBuilder problems = new StringBuilder();
for (String text : input.lines().toList()) {
try {
ok.add(parse(text));
} catch (IllegalArgumentException e) {
problems.append(" skip: ").append(e.getMessage()).append('\n');
}
}
ok.forEach(l -> System.out.printf("%s (%d) %-14s %3d%n", l.admission(), l.year(), l.name(), l.score()));
System.out.print(problems);
String notice = "Exams run from 02/11/2026 to 20/11/2026; results on 15/12/2026.";
System.out.println(DMY.matcher(notice).replaceAll("$3-$2-$1"));
}
}