Regular Expressions in Java
Learn Java regex with Pattern and Matcher — character classes, groups, named groups, lookahead, lookbehind, and real-world patterns.
Pattern and Matcher Basics
Java regex lives in java.util.regex. The two core classes are:
Pattern— compiled representation of a regex (thread-safe, reuse it)Matcher— engine that applies a Pattern to a specific input string (not thread-safe)
import java.util.regex.*;
public class RegexBasics {
public static void main(String[] args) {
// Compile once, reuse many times
Pattern digits = Pattern.compile("\\d+");
// find() — search anywhere in the string
Matcher m1 = digits.matcher("Order #1042 contains 3 items");
while (m1.find()) {
System.out.println("Found: " + m1.group() + " at " + m1.start());
}
// Found: 1042 at 7
// Found: 3 at 26
// matches() — entire input must match
System.out.println(digits.matcher("12345").matches()); // true
System.out.println(digits.matcher("123x5").matches()); // false
// Convenience methods on String
System.out.println("hello123".matches("[a-z]+\\d+")); // true
System.out.println("hello world".replaceAll("\\s+", "_")); // hello_world
System.out.println("a,b,,c".split(",").length); // 4 (note empty token)
}
}
Regex Syntax Reference
Character Classes
. any character except newline (unless DOTALL mode)
\d digit: [0-9]
\D non-digit: [^0-9]
\w word char: [a-zA-Z0-9_]
\W non-word char
\s whitespace: [ \t\r\n\f]
\S non-whitespace
[abc] a, b, or c
[^abc] anything except a, b, c
[a-z] range: lowercase letter
[a-zA-Z] letter (upper or lower)
Quantifiers
* 0 or more (greedy)
+ 1 or more (greedy)
? 0 or 1 (greedy)
{n} exactly n
{n,} n or more
{n,m} between n and m (inclusive)
*? 0 or more (lazy — minimal match)
+? 1 or more (lazy)
?? 0 or 1 (lazy)
Anchors and Boundaries
^ start of string (or line in MULTILINE mode)
$ end of string (or line in MULTILINE mode)
\b word boundary
\B non-word boundary
\A start of entire input
\Z end of entire input (before optional trailing newline)
Greedy vs Lazy
String html = "<b>bold</b> and <i>italic</i>";
// Greedy — matches as much as possible
Pattern greedy = Pattern.compile("<.+>");
Matcher mg = greedy.matcher(html);
if (mg.find()) System.out.println(mg.group());
// <b>bold</b> and <i>italic</i> — too much!
// Lazy — matches as little as possible
Pattern lazy = Pattern.compile("<.+?>");
Matcher ml = lazy.matcher(html);
while (ml.find()) System.out.println(ml.group());
// <b>
// </b>
// <i>
// </i>
Capturing Groups
Parentheses (...) create a capturing group. Access matches with group(n):
import java.util.regex.*;
public class GroupsDemo {
public static void main(String[] args) {
// Date parsing: yyyy-MM-dd
Pattern date = Pattern.compile("(\\d{4})-(\\d{2})-(\\d{2})");
Matcher m = date.matcher("Event on 2024-07-15 starts at 09:00");
if (m.find()) {
System.out.println("Full match: " + m.group(0)); // 2024-07-15
System.out.println("Year: " + m.group(1)); // 2024
System.out.println("Month: " + m.group(2)); // 07
System.out.println("Day: " + m.group(3)); // 15
}
// Iterating all matches
Pattern price = Pattern.compile("\\$(\\d+\\.\\d{2})");
Matcher pm = price.matcher("Cart: $12.99 + $3.50 + $0.99");
double total = 0;
while (pm.find()) {
total += Double.parseDouble(pm.group(1));
}
System.out.printf("Total: $%.2f%n", total); // Total: $17.48
}
}
Named Groups
Named groups (?<name>...) make patterns more readable and robust to reordering:
import java.util.regex.*;
public class NamedGroups {
public static void main(String[] args) {
// Log line: [2024-07-15 14:32:01] ERROR UserService: User 42 not found
Pattern log = Pattern.compile(
"\\[(?<date>\\d{4}-\\d{2}-\\d{2}) (?<time>\\d{2}:\\d{2}:\\d{2})\\]" +
" (?<level>\\w+) (?<logger>[\\w.]+): (?<message>.+)"
);
String line = "[2024-07-15 14:32:01] ERROR UserService: User 42 not found";
Matcher m = log.matcher(line);
if (m.matches()) {
System.out.println("Date: " + m.group("date")); // 2024-07-15
System.out.println("Time: " + m.group("time")); // 14:32:01
System.out.println("Level: " + m.group("level")); // ERROR
System.out.println("Logger: " + m.group("logger")); // UserService
System.out.println("Message: " + m.group("message")); // User 42 not found
}
// Named group in replacement
Pattern ssn = Pattern.compile("(?<first>\\d{3})-(?<mid>\\d{2})-(?<last>\\d{4})");
String masked = ssn.matcher("SSN: 123-45-6789")
.replaceAll("***-**-${last}");
System.out.println(masked); // SSN: ***-**-6789
}
}
Lookahead and Lookbehind
These assert context without consuming characters:
import java.util.regex.*;
import java.util.*;
public class LookaroundDemo {
public static void main(String[] args) {
// Positive lookahead (?=...) — match X only if followed by Y
// Find numbers followed by "px"
Pattern pxLook = Pattern.compile("\\d+(?=px)");
Matcher m1 = pxLook.matcher("font-size: 16px; margin: 8px; opacity: 0.5");
List<String> pxValues = new ArrayList<>();
while (m1.find()) pxValues.add(m1.group());
System.out.println(pxValues); // [16, 8]
// Negative lookahead (?!...) — match X only if NOT followed by Y
Pattern notPx = Pattern.compile("\\d+(?!\\.\\d|px)\\b");
// matches integers not followed by .digit or px
// Positive lookbehind (?<=...) — match X only if preceded by Y
Pattern afterDollar = Pattern.compile("(?<=\\$)\\d+\\.\\d{2}");
Matcher m2 = afterDollar.matcher("Price: $29.99, Discount: $5.00");
while (m2.find()) System.out.println(m2.group()); // 29.99 5.00
// Negative lookbehind (?<!...) — match X only if NOT preceded by Y
Pattern notAfterMinus = Pattern.compile("(?<!-)\\b\\d+\\b");
// matches numbers not preceded by a minus sign
}
}
Common Real-World Patterns
import java.util.regex.*;
public class CommonPatterns {
// Email — simplified but practical
private static final Pattern EMAIL =
Pattern.compile("^[a-zA-Z0-9._%+\\-]+@[a-zA-Z0-9.\\-]+\\.[a-zA-Z]{2,}$");
// URL
private static final Pattern URL =
Pattern.compile("https?://[\\w\\-]+(\\.[\\w\\-]+)+(/[\\w\\-._~:/?#\\[\\]@!$&'()*+,;=%]*)?");
// IPv4 address
private static final Pattern IPV4 =
Pattern.compile("^((25[0-5]|2[0-4]\\d|[01]?\\d\\d?)\\.){3}(25[0-5]|2[0-4]\\d|[01]?\\d\\d?)$");
// ISO date
private static final Pattern ISO_DATE =
Pattern.compile("^\\d{4}-(0[1-9]|1[0-2])-(0[1-9]|[12]\\d|3[01])$");
// Password: 8+ chars, at least one uppercase, one digit, one special char
private static final Pattern PASSWORD =
Pattern.compile("^(?=.*[A-Z])(?=.*\\d)(?=.*[!@#$%^&*]).{8,}$");
// Credit card (basic — 13-19 digits, optional spaces/dashes)
private static final Pattern CREDIT_CARD =
Pattern.compile("^[\\d \\-]{13,19}$");
public static boolean isValidEmail(String s) { return EMAIL.matcher(s).matches(); }
public static boolean isValidIpv4(String s) { return IPV4.matcher(s).matches(); }
public static boolean isValidIsoDate(String s) { return ISO_DATE.matcher(s).matches(); }
public static boolean isStrongPassword(String s){ return PASSWORD.matcher(s).matches(); }
public static void main(String[] args) {
System.out.println(isValidEmail("[email protected]")); // true
System.out.println(isValidEmail("not-an-email")); // false
System.out.println(isValidIpv4("192.168.1.1")); // true
System.out.println(isValidIsoDate("2024-13-01")); // false (month 13)
System.out.println(isStrongPassword("Passw0rd!")); // true
System.out.println(isStrongPassword("password")); // false
}
}
replaceAll with Logic (Java 9+)
Matcher.replaceAll(Function<MatchResult, String>) lets you compute replacements dynamically:
import java.util.regex.*;
// Replace each number with its square
String input = "values: 3 and 5 and 12";
String result = Pattern.compile("\\d+")
.matcher(input)
.replaceAll(mr -> {
int n = Integer.parseInt(mr.group());
return String.valueOf(n * n);
});
System.out.println(result); // values: 9 and 25 and 144
// Redact credit card numbers in logs
String log = "Charged card 4111-1111-1111-1234 for $99.00";
String redacted = Pattern.compile("\\b\\d{4}[- ]?\\d{4}[- ]?\\d{4}[- ]?\\d{4}\\b")
.matcher(log)
.replaceAll(mr -> "****-****-****-" + mr.group().replaceAll("[^\\d]", "").substring(12));
System.out.println(redacted); // Charged card ****-****-****-1234 for $99.00
Flags
// Case-insensitive
Pattern p1 = Pattern.compile("hello", Pattern.CASE_INSENSITIVE);
// or inline: Pattern.compile("(?i)hello")
// Multiline — ^ and $ match start/end of each line
Pattern p2 = Pattern.compile("^\\w+", Pattern.MULTILINE);
// Dotall — . matches newlines too
Pattern p3 = Pattern.compile("<div>.+?</div>", Pattern.DOTALL);
// Combine flags with |
Pattern p4 = Pattern.compile("^hello", Pattern.CASE_INSENSITIVE | Pattern.MULTILINE); Frequently Asked Questions
What is the difference between matches() and find()?
String.matches() (and Matcher.matches()) requires the entire string to match the pattern. Matcher.find() scans for a match anywhere within the string. For example, pattern '\d+' with find() matches '42' in 'price: 42', but matches() requires the entire input to be digits.
Why do Java regex patterns need double backslashes?
Java string literals use backslash as an escape character, so '\' in a string produces a single backslash. Regex also uses backslash for special sequences (\d, \w, \s). To get a literal backslash in the regex, you write '\\' in a Java string. Using raw text blocks (Java 15+) does not help here because \d still needs '\\d' in a text block.
How do I make a regex case-insensitive?
Either add the flag (?i) inside the pattern: '(?i)hello', or pass Pattern.CASE_INSENSITIVE to Pattern.compile(): Pattern.compile('hello', Pattern.CASE_INSENSITIVE).
What is a capturing group vs a non-capturing group?
A capturing group (parentheses: (abc)) captures the matched text and makes it accessible via Matcher.group(n). A non-capturing group (?:abc) groups for alternation or quantifiers without capturing — it has no group number and is slightly faster.