IntermediateJava · Lesson 8 of 9

Regular Expressions & Text Processing

Pattern and Matcher, matches vs find, named groups, replaceAll, split, StringBuilder and text blocks.

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.

TextTools.javaJava
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 $1 or a function) and regex split cover most parsing.
  • Use StringBuilder in 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.

Roster.javaJava
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"));
    }
}

Check your understanding

  1. What is the difference between matcher.matches() and matcher.find()?

  2. How do you write the regex \d+ as a Java string literal?

  3. Why use StringBuilder when building text in a loop?

  4. Why store a Pattern in a static final field?

Ask AI