Java Regular Expressions

In the last lesson you learned about Java immutable strings. Now picture real jobs like these:

  • Check that text typed into a form looks like an email before you save it.
  • Pull every phone number out of a long block of text.
  • Split a line cleanly when it has one space here and three spaces there.

Character-by-character loops handle all of this, but they are slow to write and easy to get wrong. A regular expression (“regex” for short) is a much sharper tool.

🤔 What is a regex?

A regular expression is a small pattern that describes the shape of text. You do not list every allowed value. You describe the shape, then Java checks real text against it.

  • It works like a search filter: not “find the word cat” but “find any three letters”.
  • The pattern is the rule. The text either fits or it does not.

The String method matches asks “does this whole String fit this pattern?” and hands back true or false.

String text = "hello";
System.out.println(text.matches("hello")); // exact text
System.out.println(text.matches("world")); // does not fit
  • A plain pattern with no special characters matches that exact text only.
  • The power comes from the special building blocks, which we add step by step.

Output

true
false

⚠️ The double backslash rule

Know one Java rule first, or every example will confuse you:

  • Many regex blocks start with a backslash, like \d for “a digit”.
  • In a Java String the backslash is already special, so you write two to get one.
  • So the digit pattern is "\\d" in Java code: the first backslash escapes, the second reaches the regex.

This example checks for one digit.

String text = "7";
System.out.println(text.matches("\\d")); // ✅ correct, one backslash reaches the regex

Every backslash you read in a regex, you type twice in Java. Forget the second one and the code will not compile or will behave strangely.

Two backslashes, always

In regex theory you read \d, \w, \s. In Java code you write "\\d", "\\w", "\\s". The doubling is a Java string rule, not a regex rule. This trips up almost everyone at the start.

🔤 Character classes

A character class is a set of characters in square brackets. It matches any one character from the set.

  • [abc] matches a single a, b, or c.
  • A dash makes a range: [a-z] is any lowercase letter, [0-9] is any digit.

This example checks single characters against a few classes.

System.out.println("b".matches("[abc]")); // b is in the set
System.out.println("d".matches("[abc]")); // d is not in the set
System.out.println("k".matches("[a-z]")); // any lowercase letter
System.out.println("5".matches("[0-9]")); // any digit
System.out.println("X".matches("[a-zA-Z]")); // any letter, either case

Reading the results:

  • [abc] matches b but not d, because d is not listed.
  • [a-z] matches k, since it falls in the range.
  • [0-9] matches 5, a digit.
  • [a-zA-Z] matches X, because you can combine ranges in one class.
  • A ^ as the first character inside flips the set: [^0-9] matches anything that is not a digit.

Output

true
false
true
true
true

🔢 Shorthand classes: digits, words, whitespace

Common classes get short names that save typing:

  • \d is [0-9], any single digit.
  • \w is a “word” character: a letter, a digit, or an underscore.
  • \s is any whitespace: a space, a tab, or a newline.
  • The capital version means the opposite: \D non-digit, \W non-word, \S non-whitespace.

This example uses the shorthands. In Java each one needs two backslashes.

System.out.println("4".matches("\\d")); // a digit
System.out.println("a".matches("\\w")); // a word character
System.out.println(" ".matches("\\s")); // a space
System.out.println("a".matches("\\D")); // a non-digit, true
  • "4" fits \d, "a" fits \w, a space fits \s.
  • "a" fits \D because a letter is not a digit.
  • These three cover a huge share of real patterns, so memorise them.

Output

true
true
true
true

🔁 Quantifiers: how many times

So far each pattern matched one character. A quantifier says how many times the thing before it may repeat.

  • * means zero or more times.
  • + means one or more times.
  • ? means zero or one time, so optional.
  • {n} means exactly n times, and {n,m} means between n and m times.

This example matches whole groups of characters.

System.out.println("12345".matches("\\d+")); // one or more digits
System.out.println("".matches("\\d*")); // zero or more, empty fits
System.out.println("color".matches("colou?r")); // the u is optional
System.out.println("1234".matches("\\d{4}")); // exactly four digits
System.out.println("12".matches("\\d{2,4}")); // between two and four

Walking through it:

  • \d+ matches "12345", one or more digits in a row.
  • \d* matches even the empty String "", since “zero or more” allows zero.
  • colou?r matches both "color" and "colour", because ? makes the u optional.
  • \d{4} matches exactly four digits, good for a year or a PIN.
  • \d{2,4} matches a run of two, three, or four digits.
  • A quantifier attaches to the thing right before it: \d+ repeats the digit, (ab)+ repeats the pair.

Output

true
true
true
true
true

⚓ Anchors: start and end

Anchors do not match a character. They match a position.

  • ^ means the start of the text, $ means the end.
  • They matter most when you search inside a larger block with Matcher.
  • With String matches the pattern must fit the whole String anyway, so anchors are less needed there.

This example uses an anchor with a search, not a full match.

String text = "cat catalog scatter";
// ^cat means a "cat" that sits at the very start
System.out.println(text.replaceAll("^cat", "DOG"));
  • ^cat matches only the cat at the very start, not the ones inside catalog or scatter.
  • So only the first word changes; without ^, every cat would be replaced.

Output

DOG catalog scatter

🧱 Groups and alternation

A group is a pattern wrapped in round brackets ( ). It does two jobs:

  • Lets a quantifier repeat a whole chunk.
  • Lets you pull that chunk out later.
  • Alternation | means “or”: cat|dog matches either cat or dog.

This example shows both.

System.out.println("abab".matches("(ab)+")); // repeat the whole pair
System.out.println("cat".matches("cat|dog")); // cat or dog
System.out.println("dog".matches("cat|dog")); // dog also fits
System.out.println("yesyesyes".matches("(yes){3}"));// exactly three "yes"

Reading it:

  • (ab)+ matches "abab", the group ab repeating one or more times.
  • cat|dog matches both "cat" and "dog", since either side of | is allowed.
  • (yes){3} matches "yesyesyes", the whole group exactly three times.
  • Groups shine when you want to extract part of the text, which we do with Matcher next.

Output

true
true
true
true

✂️ Escaping special characters

Some characters have special meaning in regex: ., *, +, ?, (, ), [, ], |, \.

  • The dot . on its own means “any single character”, not a literal full stop.
  • To match a real dot, escape it with a backslash: \..
  • Since this is Java, that backslash becomes two: "\\.".

This example shows the difference.

System.out.println("a".matches(".")); // dot means any character
System.out.println("ab".matches("a.b")); // any character in the middle
System.out.println("a.b".matches("a\\.b")); // ✅ escaped dot, a real dot only
System.out.println("axb".matches("a\\.b")); // false, x is not a real dot
  • . matches any one character, so "a" fits it and a.b matches axb.
  • Escaped as \. ("\\." in Java), it matches a real dot only.
  • This is why splitting a String on a dot needs "\\.", not just ".".

Output

true
true
true
false

🛠️ The String regex shortcuts

The String class has four methods that take a regex. For quick one-off jobs these are all you need.

  • matches(regex) returns true only if the whole String fits the pattern.
  • replaceAll(regex, replacement) swaps every match for the replacement text.
  • replaceFirst(regex, replacement) swaps only the first match.
  • split(regex) breaks the String into an array wherever the pattern matches.

This example uses all four.

String text = "Order 42 shipped, order 99 pending";
System.out.println("abc123".matches("\\w+")); // all word chars
System.out.println(text.replaceAll("\\d+", "#")); // every number
System.out.println(text.replaceFirst("\\d+", "#")); // only the first
System.out.println("a b c".split("\\s+").length); // split on spaces

What each line does:

  • matches("\\w+") is true because "abc123" is all word characters.
  • replaceAll("\\d+", "#") turns every run of digits into a #.
  • replaceFirst("\\d+", "#") changes only the first number, leaving 99 alone.
  • split("\\s+") breaks on one or more spaces, so messy gaps collapse into three pieces.
  • Plain split(" ") would leave empty strings between extra spaces; \s+ avoids that.

Output

true
Order # shipped, order # pending
Order # shipped, order 99 pending
3

🧰 Pattern and Matcher for reuse and searching

The String shortcuts are fine for a one-time check, but they have two limits:

  • They rebuild the pattern every call, which is wasteful in a loop.
  • They cannot easily find and loop over many matches inside a longer text.

For both jobs use Pattern and Matcher. Compile the pattern once into a Pattern, then ask it for a Matcher on some text, and the Matcher does the searching.

This example compiles a pattern once, then reuses it.

import java.util.regex.Pattern;
import java.util.regex.Matcher;
public class Main {
public static void main(String[] args) {
Pattern p = Pattern.compile("\\d+"); // compile once
Matcher m = p.matcher("a1b22c333"); // point it at some text
while (m.find()) { // find each match in turn
System.out.println(m.group()); // the matched text
}
}
}

Here is the flow:

  • Pattern.compile("\\d+") builds the pattern object once.
  • p.matcher(...) creates a Matcher aimed at the text to search.
  • m.find() moves to the next match and returns true while one remains.
  • m.group() hands back the text the last find() matched.
  • So the loop prints every run of digits, the standard way to pull pieces from a larger String.

Output

1
22
333

🎯 Capturing groups with Matcher

When you search with a Matcher, each group gets a number:

  • group(0) is the whole match.
  • group(1) is the first bracketed group, group(2) the second, and so on.
  • This pulls out exactly the part you care about.

This example reads a date and pulls the day, month, and year apart.

import java.util.regex.Pattern;
import java.util.regex.Matcher;
public class Main {
public static void main(String[] args) {
Pattern p = Pattern.compile("(\\d{2})/(\\d{2})/(\\d{4})");
Matcher m = p.matcher("Today is 13/06/2026 here");
if (m.find()) {
System.out.println("Whole: " + m.group(0));
System.out.println("Day: " + m.group(1));
System.out.println("Month: " + m.group(2));
System.out.println("Year: " + m.group(3));
}
}
}
  • The pattern has three groups, one per part of the date.
  • After find(), group(0) is the full 13/06/2026; group(1)group(3) give the three parts.
  • This is far cleaner than chopping the String up by hand with substring.

Output

Whole: 13/06/2026
Day: 13
Month: 06
Year: 2026

📧 Practical example: validate an email

Let’s check that a String looks like a basic email. The shape: some word-ish characters, an @, a domain, a dot, then an ending like com or org.

Here is a simple pattern that covers most everyday emails.

public class Main {
public static void main(String[] args) {
String pattern = "[\\w.]+@[\\w.]+\\.[a-z]{2,}";
System.out.println("alex@example.com".matches(pattern)); // valid
System.out.println("riya.k@mail.co".matches(pattern)); // valid
System.out.println("not-an-email".matches(pattern)); // no @
System.out.println("a@b".matches(pattern)); // no .ending
}
}

Reading the pattern piece by piece:

  • [\w.]+ is one or more word characters or dots, the name part.
  • @ is a literal @.
  • [\w.]+ is the domain.
  • \. is a real, escaped dot.
  • [a-z]{2,} is an ending of at least two letters, like com.
  • So a clean address fits and the broken ones do not.

Real email rules are surprisingly complex. This is a friendly “good enough” version, not the full official rule, and it works well for most forms.

Output

true
true
false
false

📞 Practical example: validate a phone and extract numbers

Two quick tasks: check that a phone number is exactly ten digits, and pull every number out of a sentence.

This example does both.

import java.util.regex.Pattern;
import java.util.regex.Matcher;
public class Main {
public static void main(String[] args) {
// a phone is exactly 10 digits
System.out.println("9876543210".matches("\\d{10}")); // true
System.out.println("98765".matches("\\d{10}")); // too short
// pull every number out of a sentence
Pattern p = Pattern.compile("\\d+");
Matcher m = p.matcher("Bought 3 pens and 12 books for 250 rupees");
while (m.find()) {
System.out.println(m.group());
}
}
}
  • \d{10} means exactly ten digits and nothing else, so a full number passes and a short one fails.
  • \d+ plus a find() loop pulls out each run of digits in order.
  • This is the same Matcher pattern you saw before, now doing real work.

Output

true
false
3
12
250

🪓 Practical example: split on messy spaces

Real-world data is rarely tidy. A line might have one space here and several there.

  • Split on a single space and you get empty pieces.
  • Split on \s+ (“one or more whitespace”) and runs of spaces collapse into one separator.

This example compares the two ways.

public class Main {
public static void main(String[] args) {
String messy = "Alex Riya Sam";
String[] bad = messy.split(" "); // ❌ extra spaces make empty pieces
String[] good = messy.split("\\s+"); // ✅ collapses runs of spaces
System.out.println("Bad count: " + bad.length);
System.out.println("Good count: " + good.length);
for (String name : good) {
System.out.println(name);
}
}
}
  • Splitting on a single space counts gaps wrong: each extra space creates an empty entry.
  • Splitting on \s+ treats any run of spaces as one separator, giving the three clean names.
  • This one trick fixes a huge share of “why is my array the wrong size?” bugs.

Output

Bad count: 7
Good count: 3
Alex
Riya
Sam

⚠️ Common Mistakes

A few regex slip-ups catch nearly everyone.

Forgetting the double backslash. Every regex backslash must be doubled in Java, or it will not compile.

String s = "5";
s.matches("\d"); // ❌ will not compile, single backslash is invalid in Java
s.matches("\\d"); // ✅ two backslashes, the regex sees one

Thinking matches searches inside the text. The matches method only returns true if the whole String fits the pattern, not just a part of it.

String s = "the year is 2026";
System.out.println(s.matches("\\d+")); // ❌ false, the whole String is not all digits
System.out.println(s.matches(".*\\d+.*")); // ✅ true, .* allows text around the digits

To know whether a pattern appears somewhere inside, use a Matcher with find(), or wrap the pattern with .* on both sides.

Forgetting greedy versus lazy matching. Quantifiers like + and * are greedy by default, grabbing as much as they can. Add ? after them to make them lazy, grabbing as little as possible.

import java.util.regex.Pattern;
import java.util.regex.Matcher;
public class Main {
public static void main(String[] args) {
String text = "<a><b>";
Matcher g = Pattern.compile("<.+>").matcher(text);
g.find();
System.out.println(g.group()); // ❌ greedy, grabs the whole "<a><b>"
Matcher l = Pattern.compile("<.+?>").matcher(text);
l.find();
System.out.println(l.group()); // ✅ lazy, stops at the first ">", gives "<a>"
}
}
  • The greedy <.+> stretches to the last >, swallowing everything.
  • The lazy <.+?> stops at the first >, usually what you want.

✅ Best Practices

Habits that keep your regex code clean and fast.

  • Compile once, reuse the Pattern. If the same pattern runs many times, build it with Pattern.compile outside the loop instead of calling String.matches over and over.
  • Use find() to search and matches() to validate. matches checks the whole String. find looks for a match anywhere inside.
  • Always double your backslashes. Read \d in your head, type "\\d" in your code.
  • Escape characters that have meaning. A real dot is \., a real bracket is \(, and so on, both doubled in Java.
  • Make quantifiers lazy when you want the shortest match. Add ? to turn + into +? and * into *?.
  • Keep patterns simple and readable. A pattern that is “good enough” and clear beats a perfect monster nobody can read later.

🧩 What You’ve Learned

Regex is a tool you will reach for again and again. The key ideas:

  • ✅ A regex is a pattern that describes the shape of text, and matches checks the whole String against it.
  • ✅ In Java you write every regex backslash twice, so a digit is "\\d".
  • ✅ Building blocks: character classes [a-z], shorthands \d \w \s, anchors ^ $, quantifiers * + ? {n}, groups ( ), and alternation |.
  • ✅ Escape special characters like the dot with \. to match them literally.
  • ✅ The String shortcuts matches, replaceAll, replaceFirst, and split cover quick jobs.
  • ✅ Use Pattern.compile once, then a Matcher with find() and group() to search a long text and pull out parts.

Check Your Knowledge

Test what you learned. Pick an answer for each question, then click Check.

  1. 1

    How do you write the regex digit pattern in a Java String?

    Why: Java needs the backslash doubled, so \d in regex is written "\\d" in Java code.

  2. 2

    What does the String matches method check?

    Why: matches returns true only when the entire String fits the pattern, not just a part of it.

  3. 3

    Which method do you call to step through every match in a longer text?

    Why: matcher.find() moves to the next match and returns true while matches remain.

  4. 4

    What does the quantifier + mean?

    Why: The + quantifier means the thing before it must appear one or more times.

🚀 What’s Next?

You can now match, search, and pull text apart with confidence. The next stop is working with dates and times, another everyday job that has its own handy classes. Let’s look at it next.

Java Date Class

Share & Connect