This collection of 30 Java regex exercises builds your pattern-matching skills from simple literal matches all the way to lookaheads, lookbehinds, and backreferences.
- Early exercises cover character classes, quantifiers, anchors, and word boundaries
- The middle set validates real-world formats like dates, times, hex colors, IP addresses, and phone numbers using capturing groups.
- The final exercises tackle non-greedy matching, negative lookahead, CamelCase splitting, password strength validation, and inserting thousands separators with zero-width assertions.
Each exercise includes a Problem Statement, Purpose, Hint, and a Solution with Explanation.
- Also, See: Java Exercises with over 20+ topic-wise sets and 575+ coding questions to practice.
- Practice questions using our Online Java Compiler
+ Table of Contents (30 Exercises)
Table of contents
- Exercise 1: Exact String Match
- Exercise 2: Case-Insensitive Match
- Exercise 3: Digits Only
- Exercise 4: Alphanumeric Only
- Exercise 5: Vowel Count
- Exercise 6: No Digits
- Exercise 7: Fixed Length Identifier
- Exercise 8: Word Boundary
- Exercise 9: Starts & Ends With
- Exercise 10: Hex Color Code
- Exercise 11: Whitespace Trimming
- Exercise 12: Binary Number
- Exercise 13: Variable Names
- Exercise 14: Date Format (YYYY-MM-DD)
- Exercise 15: Time Format (24-Hour)
- Exercise 16: Extract Domain Name
- Exercise 17: Duplicate Words
- Exercise 18: MAC Address
- Exercise 19: IP Address (IPv4)
- Exercise 20: Phone Number Formatting
- Exercise 21: HTML Tag Extractor
- Exercise 22: CamelCase Splitter
- Exercise 23: CSV Parser
- Exercise 24: Password Strength
- Exercise 25: Match Numbers NOT Followed by %
- Exercise 26: Match Words NOT Starting with ‘un’
- Exercise 27: Floating Point Numbers
- Exercise 28: Remove Multi-line Comments
- Exercise 29: Extract Email Components
- Exercise 30: Thousands Separator
Exercise 1: Exact String Match
Problem Statement: Write a regex to match the exact word "Java" (case-sensitive).
Purpose: This exercise introduces the most basic form of a regex, a literal string, and shows that String.matches() compares against the entire input, not just a portion of it.
Given Input: testing "Java", "java", and "JAVA" against the pattern "Java"
Expected Output:
true false false
▼ Hint
- A regex made only of plain letters matches those exact characters and nothing else.
- Regex matching in Java is case-sensitive by default, so uppercase and lowercase letters are treated as completely different characters.
String.matches(regex)requires the entire string to match the pattern, not just part of it.
▼ Solution & Explanation
Explanation:
String regex = "Java": A literal pattern with no special regex syntax at all. It matches only the exact sequence of charactersJ,a,v,a."Java".matches(regex): Returnstruesince the input is character-for-character identical to the pattern."java".matches(regex)and"JAVA".matches(regex): Both returnfalse, since regex matching distinguishes between uppercase and lowercase letters unless told otherwise.- Whole-string matching:
matches()implicitly anchors the pattern to both the start and end of the input, unlike some other regex methods that only look for a match anywhere inside the string.
Exercise 2: Case-Insensitive Match
Problem Statement: Match the word "java" regardless of case (e.g., Java, JAVA, JaVa).
Purpose: This exercise introduces the inline flag (?i), which switches a pattern into case-insensitive mode without needing a separate compilation flag passed in code.
Given Input: testing "Java", "JAVA", and "JaVa" against the pattern "(?i)java"
Expected Output:
true true true
▼ Hint
- Place
(?i)at the very start of the pattern to enable case-insensitive matching for the rest of the regex. - This inline flag affects every letter that follows it in the pattern, not just the next character.
- No changes are needed to the input strings themselves, only to the pattern.
▼ Solution & Explanation
Explanation:
(?i): An inline flag that turns on case-insensitive matching for the remainder of the pattern, embedded directly in the regex string itself.(?i)java: With the flag active, this pattern now matchesjava,Java,JAVA, and any other capitalization combination of the same four letters.- All three inputs return
true: Since the flag ignores case entirely, the specific mix of uppercase and lowercase letters in each test string no longer matters. - Alternative:
Pattern.compile("java", Pattern.CASE_INSENSITIVE)achieves the same result using a compiledPatternobject and an explicit flag constant instead of the inline syntax.
Exercise 3: Digits Only
Problem Statement: Validate a string that contains exactly 5 digits (e.g., a US zip code like 90210).
Purpose: This exercise introduces the \d shorthand character class for digits, combined with a quantifier that requires an exact repeat count.
Given Input: testing "90210", "9021", and "902100" against the pattern "\\d{5}"
Expected Output:
true false false
▼ Hint
\dis a shorthand character class that matches any single digit from 0 to 9.- In a Java string literal, the backslash must itself be escaped, so the pattern is written as
"\\d". - The
{5}quantifier requires exactly 5 repetitions of whatever comes immediately before it.
▼ Solution & Explanation
Explanation:
\\d: In the Java string literal, this represents the single regex token\d, which matches any one digit character.{5}: An exact-count quantifier, requiring precisely 5 digits in a row, no more and no fewer."9021".matches(regex): Returnsfalsebecause the string only has 4 digits, one short of the required count."902100".matches(regex): Returnsfalsebecause the string has 6 digits, one too many, sincematches()requires the entire string to satisfy the pattern.
Exercise 4: Alphanumeric Only
Problem Statement: Check if a string contains only alphanumeric characters (letters and numbers) and no spaces or special characters.
Purpose: This exercise introduces a custom character class built with square brackets, combining letter ranges and digit ranges into one set of allowed characters.
Given Input: testing "Java123", "Java 123", and "Java_123" against the pattern "[a-zA-Z0-9]+"
Expected Output:
true false false
▼ Hint
- Square brackets define a character class, a set of characters where any single one of them counts as a match.
- A hyphen inside a character class, like
a-z, defines a range rather than a literal hyphen character. - The
+quantifier after the character class requires one or more matching characters across the whole string.
▼ Solution & Explanation
Explanation:
[a-zA-Z0-9]: A character class combining three ranges: lowercase letters, uppercase letters, and digits, so any single character from any of those three ranges counts as a match.+: Requires the entire string to consist of one or more characters from that character class, with nothing else mixed in."Java 123".matches(regex): Returnsfalsebecause the space character is not part of the allowed character class."Java_123".matches(regex): Returnsfalsebecause the underscore is also outside the defined ranges, even though underscores are sometimes informally grouped with “alphanumeric” characters.
Exercise 5: Vowel Count
Problem Statement: Match any single English vowel (both lowercase and uppercase).
Purpose: This exercise introduces Pattern and Matcher together with a find() loop, the standard approach for locating every occurrence of a pattern inside a larger string, rather than checking the whole string at once.
Given Input: "Regular Expressions are Powerful", searched with the pattern "[AEIOUaeiou]"
Expected Output: Vowel count: 12
▼ Hint
- List all ten vowel characters, both cases, inside a single character class.
- Compile the pattern with
Pattern.compile(), then create aMatcherfor the input string withpattern.matcher(input). - Call
find()repeatedly in awhileloop. Each call advances to the next match, and it returnsfalseonce there are no more matches left.
▼ Solution & Explanation
Explanation:
[AEIOUaeiou]: A character class listing all five vowels in both uppercase and lowercase, so any single one of those ten characters counts as a match.Pattern.compile(regex): Compiles the pattern once into a reusablePatternobject, which is more efficient than recompiling the same regex on every match attempt.pattern.matcher(input): Creates aMatchertied to this specific input string, ready to search through it for matches.while (matcher.find()): Each call tofind()locates the next vowel starting from wherever the previous match left off, letting the loop count every single vowel in the string.
Exercise 6: No Digits
Problem Statement: Match a string that contains absolutely no numeric digits.
Purpose: This exercise introduces a negated character class, which matches any character except the ones listed, the opposite approach from the earlier allow-list style character classes.
Given Input: testing "Hello World" and "Hello123" against the pattern "[^0-9]*"
Expected Output:
true false
▼ Hint
- A
^as the very first character inside a character class negates it, meaning “match anything except these”. [^0-9]matches any character that is not a digit from 0 to 9.- Use
*rather than+if an empty string should also be considered a valid, digit-free match.
▼ Solution & Explanation
Explanation:
[^0-9]: The caret at the start of the character class inverts it, so this matches any character that is not one of the digits 0 through 9, including letters, spaces, and punctuation.*: Allows zero or more of those non-digit characters, so the entire string is accepted as long as it never contains a digit anywhere."Hello World".matches(regex): Returnstrue, since letters and the space character are all outside the negated digit class."Hello123".matches(regex): Returnsfalse, since the digits1,2, and3fall inside the excluded range, breaking the match for the whole string.- Alternative:
"\\D*"uses the built-in shorthand for “non-digit” and behaves identically to the custom negated character class shown here.
Exercise 7: Fixed Length Identifier
Problem Statement: Match a string that starts with a letter followed by exactly 3 digits (e.g., A123, z987).
Purpose: This exercise combines two different pattern pieces back to back, a single-letter character class followed by a fixed-count digit group, showing how a regex is built from smaller, sequential building blocks.
Given Input: testing "A123", "z987", "AB123", and "A12" against the pattern "[A-Za-z]\\d{3}"
Expected Output:
true true false false
▼ Hint
- Start the pattern with a character class matching exactly one letter, upper or lowercase.
- Follow it immediately with
\d{3}to require exactly 3 digits right after that letter. - Since there is no quantifier on the letter class itself, it implicitly matches exactly one character.
▼ Solution & Explanation
Explanation:
[A-Za-z]: Matches exactly one letter, either uppercase or lowercase, since a character class with no quantifier always matches a single character.\\d{3}: Immediately requires exactly 3 digits right after that letter, with no separator or gap allowed between them."AB123".matches(regex): Returnsfalsebecause there are two letters at the start instead of one, which the pattern does not allow."A12".matches(regex): Returnsfalsebecause only 2 digits follow the letter, one short of the required 3.
Exercise 8: Word Boundary
Problem Statement: Find the word "cat" only when it stands alone, not inside other words like "category" or "bobcat".
Purpose: This exercise introduces the \b word boundary anchor, which matches the invisible transition between a word character and a non-word character, without consuming any characters itself.
Given Input: "The cat sat near the category and the bobcat.", searched with the pattern "\\bcat\\b"
Expected Output: Match found at index: 4
▼ Hint
- Place
\bon both sides ofcatto require a word boundary immediately before and after it. - A word boundary exists between a letter and a space, or between a letter and the very start or end of the string, but not between two letters.
- Use
matcher.start()inside afind()loop to report where each match begins in the original string.
▼ Solution & Explanation
Explanation:
\\bcat\\b: Requires a word boundary immediately before thecand immediately after the secondt, ensuringcatis not attached to any other letters on either side.- Why
"category"does not match: Although it starts withcat, the character immediately after those three letters ise, another word character, so there is no word boundary there and the match fails. - Why
"bobcat"does not match: The character immediately beforecatisb, another word character, so there is no word boundary on that side either. matcher.start(): Reports the index where the current match begins in the original string, which is4for the standalone"cat"in"The cat sat...".
Exercise 9: Starts & Ends With
Problem Statement: Check if a sentence starts with a capital letter and ends with a period (.).
Purpose: This exercise practices anchoring specific requirements to the beginning and end of a string, with a flexible middle section that can contain almost anything.
Given Input: testing "This is a sentence.", "this is a sentence.", and "This is a sentence" against the pattern "[A-Z].*\\."
Expected Output:
true false false
▼ Hint
- Start the pattern with
[A-Z]to require the very first character to be an uppercase letter. - Use
.*in the middle to allow any number of any characters at all. - A period is a special regex character meaning “any character”, so a literal period at the end must be escaped as
\..
▼ Solution & Explanation
Explanation:
[A-Z]: Requires the first character of the string to be an uppercase letter, immediately ruling out sentences that start lowercase..*: Matches any sequence of characters, including none at all, filling the gap between the required first letter and the required final period.\\.: Matches a literal period character. Without the backslash, a plain.in regex means “any character”, not specifically a period."This is a sentence".matches(regex): Returnsfalsebecause the string has no trailing period at all, failing the final required character.
Exercise 10: Hex Color Code
Problem Statement: Validate a CSS hex color code (e.g., #FFF, #abc123, #12A4B6).
Purpose: This closing exercise combines a literal character, a character class, and grouped alternation, matching real-world CSS syntax that accepts either a 3-digit or a 6-digit hex code.
Given Input: testing "#FFF", "#abc123", "#12A4B6", "#12345", and "123456" against the pattern "#([A-Fa-f0-9]{6}|[A-Fa-f0-9]{3})"
Expected Output:
true true true false false
▼ Hint
- The pattern must start with a literal
#, which is not a special regex character and needs no escaping. - Hex digits include both numbers and the letters A through F, so the character class needs
0-9,A-F, anda-fto cover both letter cases. - Use parentheses to group two alternatives separated by
|, one requiring exactly 6 hex digits and the other requiring exactly 3.
▼ Solution & Explanation
Explanation:
#: Matched literally at the very start, requiring every valid input to begin with the hash symbol used in CSS hex color syntax.([A-Fa-f0-9]{6}|[A-Fa-f0-9]{3}): A group containing two alternatives joined by|, matching either exactly 6 hex digits or exactly 3, covering both the shorthand and full CSS hex formats."#12345".matches(regex): Returnsfalsebecause 5 digits satisfies neither the 6-digit nor the 3-digit alternative."123456".matches(regex): Returnsfalsebecause the string is missing the required leading#, even though the 6 hex digits themselves would otherwise be valid.
Exercise 11: Whitespace Trimming
Problem Statement: Match leading or trailing whitespaces in a string (useful for implementing a manual trim()).
Purpose: This exercise introduces the ^ and $ anchors combined with alternation, and uses replaceAll() to remove matched text rather than just checking whether a match exists.
Given Input: " Hello World "
Expected Output: "Hello World"
▼ Hint
\smatches any whitespace character, including spaces, tabs, and newlines.^\s+matches one or more whitespace characters at the very start of the string, while\s+$matches them at the very end.- Combine both with
|so a single pattern covers leading and trailing whitespace, then pass it toreplaceAll()with an empty replacement.
▼ Solution & Explanation
Explanation:
^\\s+: Matches one or more whitespace characters anchored to the very beginning of the string.\\s+$: Matches one or more whitespace characters anchored to the very end of the string.|: Combines both alternatives, soreplaceAll()strips matches from either end without touching whitespace in the middle of the string.input.replaceAll(regex, ""): Replaces every match with an empty string, effectively deleting the leading and trailing whitespace while leaving the single space betweenHelloandWorlduntouched.
Exercise 12: Binary Number
Problem Statement: Check if a string represents a valid binary number (contains only 0s and 1s).
Purpose: This exercise reinforces a simple character class restricted to just two characters, combined with the + quantifier to require at least one digit.
Given Input: testing "101010", "10102", and "" against the pattern "[01]+"
Expected Output:
true false false
▼ Hint
- A character class listing only
0and1restricts matches to just binary digits. - Use
+rather than*so an empty string does not count as a valid binary number. - Any character outside the class, like the
2in"10102", breaks the whole-string match.
▼ Solution & Explanation
Explanation:
[01]: A character class containing only two possible characters,0and1, matching one binary digit at a time.+: Requires at least one character from the class, so the whole string must consist entirely of 0s and 1s, with at least one digit present."10102".matches(regex): Returnsfalsebecause the digit2falls outside the allowed character class."".matches(regex): Returnsfalsebecause+requires at least one matching character, and an empty string has none.
Exercise 13: Variable Names
Problem Statement: Validate if a string is a valid Java variable identifier (starts with a letter, $, or _, followed by alphanumeric characters, $, or _).
Purpose: This exercise practices using two different character classes back to back, one for the restricted first character and a broader one for every character after it.
Given Input: testing "_count", "$value", "2ndPlace", and "user_Name1" against the pattern "[a-zA-Z_$][a-zA-Z0-9_$]*"
Expected Output:
true true false true
▼ Hint
- The first character class should exclude digits, since Java identifiers cannot start with a number.
- The second character class, applied with
*, can include digits along with letters,$, and_. - Using
*rather than+on the second class allows single-character identifiers, since zero additional characters is also valid.
▼ Solution & Explanation
Explanation:
[a-zA-Z_$]: Matches exactly one character for the required first position, restricted to letters, underscore, or dollar sign, deliberately excluding digits.[a-zA-Z0-9_$]*: Matches zero or more characters for everything after the first position, this time including digits as well."2ndPlace".matches(regex): Returnsfalsebecause the very first character is a digit, which the first character class does not permit."user_Name1".matches(regex): Returnstrue, since it starts with a letter and every character after that, including the underscore and trailing digit, falls within the second, broader character class.
Exercise 14: Date Format (YYYY-MM-DD)
Problem Statement: Match a date in the YYYY-MM-DD format (basic digit checking, e.g., 2026-12-31).
Purpose: This exercise practices combining fixed-length digit groups with literal separator characters, a common shape for structured identifiers like dates, without yet validating the actual calendar values.
Given Input: testing "2026-12-31", "26-12-31", and "2026/12/31" against the pattern "\\d{4}-\\d{2}-\\d{2}"
Expected Output:
true false false
▼ Hint
- Break the pattern into three digit groups matching the year, month, and day segments.
- The hyphen between digit groups is a literal character here, not a regex range, since it sits outside any square brackets.
- This basic version only checks digit counts and separators. It does not verify that the month is between 01 and 12 or that the day is valid for that month.
▼ Solution & Explanation
Explanation:
\\d{4}: Matches exactly 4 digits for the year portion.-\\d{2}-\\d{2}: Matches a literal hyphen, exactly 2 digits for the month, another literal hyphen, and exactly 2 digits for the day."26-12-31".matches(regex): Returnsfalsebecause the year portion only has 2 digits instead of the required 4."2026/12/31".matches(regex): Returnsfalsebecause the separators are forward slashes rather than the literal hyphens the pattern requires.
Exercise 15: Time Format (24-Hour)
Problem Statement: Match a 24-hour time format string like 14:30 or 09:15 (assume hours 00-23 and minutes 00-59).
Purpose: This exercise practices real range validation using alternation, going beyond a simple digit count to actually restrict which numeric ranges are valid.
Given Input: testing "14:30", "09:15", "24:00", and "13:65" against the pattern "([01]\\d|2[0-3]):[0-5]\\d"
Expected Output:
true true false false
▼ Hint
- Valid hours split naturally into two ranges:
00–19and20–23, which need separate alternatives since a single digit range cannot express both. [01]\dcovers hours 00 through 19, while2[0-3]covers hours 20 through 23.- Minutes are simpler, since every valid value from 00 to 59 shares the same first-digit range of 0 through 5.
▼ Solution & Explanation
Explanation:
([01]\\d|2[0-3]): A grouped alternative for the hour portion. The first branch,[01]\d, covers00through19, and the second branch,2[0-3], covers20through23.:[0-5]\\d: Requires a literal colon, followed by minutes restricted to00through59, since the first minute digit can only range from 0 to 5."24:00".matches(regex): Returnsfalsebecause24is not covered by either hour alternative, correctly rejecting an hour value that does not exist on a 24-hour clock."13:65".matches(regex): Returnsfalsebecause65is outside the valid00–59minute range, even though the hour portion,13, is perfectly valid.
Exercise 16: Extract Domain Name
Problem Statement: Extract the domain name from a URL (e.g., extract google.com from https://www.google.com/search).
Purpose: This exercise introduces capturing groups, using parentheses to isolate and extract just one meaningful part of a larger match, rather than matching or rejecting the whole string.
Given Input: "https://www.google.com/search"
Expected Output: Domain: google.com
▼ Hint
- Match the protocol first with something like
https?://, where the?makes thesinhttpsoptional so bothhttpandhttpsmatch. - Use a non-capturing group,
(?:www\.)?, to optionally skip over a leadingwww.without including it in the captured result. - Wrap the actual domain portion in parentheses to capture it, then retrieve it afterward with
matcher.group(1).
▼ Solution & Explanation
Explanation:
https?://: Matches eitherhttp://orhttps://, since the?makes the precedingsoptional.(?:www\\.)?: A non-capturing group, marked by?:, that optionally matches a leadingwww.without that text becoming part of any captured group.([a-zA-Z0-9.-]+): A capturing group, marked by plain parentheses, matching the actual domain name made of letters, digits, dots, and hyphens.matcher.group(1): Retrieves the text captured by the first parenthesized group, which is exactly the domain portion, separate from the protocol, the optionalwww., and the trailing/searchpath.
Exercise 17: Duplicate Words
Problem Statement: Find duplicate, consecutive words in a sentence (e.g., "The the movie was great").
Purpose: This exercise introduces backreferences, which let a pattern refer back to text captured earlier in the same match, essential for detecting repetition rather than a fixed literal value.
Given Input: "The the movie was was great"
Expected Output:
Duplicate found: The Duplicate found: was
▼ Hint
- Capture a word with
(\w+), then require the exact same text again using the backreference\1. - Allow one or more whitespace characters between the two occurrences with
\s+. - Compile the pattern with
Pattern.CASE_INSENSITIVEso a capitalized word at the start of a sentence still matches its lowercase repeat.
▼ Solution & Explanation
Explanation:
(\\w+): Captures one or more word characters as the first group, representing whatever word is found first.\\1: A backreference to the first captured group, requiring the exact same text to appear again, not just any word.Pattern.CASE_INSENSITIVE: Lets"The"and"the"count as the same repeated word, even though they differ in capitalization.- Two separate matches:
find()locates"The the"first, then continues scanning from where that match ended and locates"was was"as a second, independent match.
Exercise 18: MAC Address
Problem Statement: Validate a standard MAC address (e.g., 01:23:45:67:89:ab or A1-B2-C3-D4-E5-F6).
Purpose: This exercise combines a repeated group with an exact count quantifier, matching a structured, multi-segment identifier that can use either of two different separator characters.
Given Input: testing "01:23:45:67:89:ab", "A1-B2-C3-D4-E5-F6", "01:23:45:67:89", and "01:23:45:67:89:gh" against the pattern "([0-9A-Fa-f]{2}[:-]){5}[0-9A-Fa-f]{2}"
Expected Output:
true true false false
▼ Hint
- A MAC address is 6 pairs of hex digits, separated by either a colon or a hyphen.
- Group one hex pair plus its trailing separator together, then repeat that group exactly 5 times with
{5}. - The final, sixth hex pair has no trailing separator, so it needs to be matched separately after the repeated group.
▼ Solution & Explanation
Explanation:
([0-9A-Fa-f]{2}[:-]): A group matching exactly 2 hex digits followed by either a colon or a hyphen.{5}: Repeats that entire group exactly 5 times, accounting for the first 5 hex pairs and their trailing separators.[0-9A-Fa-f]{2}: Matches the final, sixth hex pair on its own, with no trailing separator required after it."01:23:45:67:89:gh".matches(regex): Returnsfalsebecausegandhare not valid hex digits, falling outside the0-9A-Fa-frange.
Exercise 19: IP Address (IPv4)
Problem Statement: Validate a standard IPv4 address (ensure each octet is between 0 and 255).
Purpose: This exercise practices real numeric range validation across a repeated structure, building a reusable sub-pattern for one octet and then combining four of them with literal dot separators.
Given Input: testing "192.168.1.1", "255.255.255.255", "256.100.50.25", and "192.168.1"
Expected Output:
true true false false
▼ Hint
- Build a single sub-pattern for “one valid octet” first, covering
250–255,200–249, and0–199as three separate alternatives. - Reuse that same sub-pattern string four times, joined by literal, escaped dots.
- Building the full pattern by concatenating a repeated substring in code keeps the regex far more readable than writing it out by hand four times.
▼ Solution & Explanation
Explanation:
25[0-5]: The first alternative in the octet pattern, covering250through255.2[0-4]\\d: The second alternative, covering200through249.[01]?\\d?\\d: The third alternative, covering everything from0through199, including single and double digit values.- Building the full pattern in code: Concatenating the
octetstring four times with escaped dots keeps the final regex readable, avoiding a single massive, repeated block of near-identical alternation written out by hand. "256.100.50.25".matches(regex): Returnsfalsebecause256exceeds the maximum valid octet value of 255, and none of the three alternatives accept it.
Exercise 20: Phone Number Formatting
Problem Statement: Match and group a 10-digit US phone number into 3 groups: area code, prefix, and line number (e.g., (123) 456-7890 or 123-456-7890).
Purpose: This closing exercise combines optional literal characters with multiple capturing groups, extracting structured pieces out of a phone number regardless of which common formatting style was used.
Given Input: "(123) 456-7890" and "123-456-7890"
Expected Output:
Area: 123, Prefix: 456, Line: 7890 Area: 123, Prefix: 456, Line: 7890
▼ Hint
- Make the parentheses around the area code optional with
\(?and\)?, since not every format includes them. - Allow an optional separator, such as a space, hyphen, or dot, between each group using a character class with a
?quantifier. - Wrap each group of digits, the area code, prefix, and line number, in its own capturing parentheses so they can be retrieved individually afterward.
▼ Solution & Explanation
Explanation:
\\(?(\\d{3})\\)?: Matches an optional opening parenthesis, then captures exactly 3 digits as the area code, then an optional closing parenthesis.[-.\\s]?: Matches an optional single separator character, either a hyphen, a dot, or any whitespace character, between each group of digits.(\\d{3})and(\\d{4}): Capture the 3-digit prefix and the 4-digit line number as two more separate groups, following the same digits-then-optional-separator pattern.- Same three groups, two formats: Both
"(123) 456-7890"and"123-456-7890"produce identical captured groups, since the optional parentheses and flexible separator character class accommodate both styles with the same single pattern.
Exercise 21: HTML Tag Extractor
Problem Statement: Extract the text content sitting inside an HTML <h1> tag (e.g., extract "Hello" from <h1>Hello</h1>).
Purpose: This exercise introduces the reluctant (non-greedy) quantifier *?, which stops at the first possible match instead of consuming as much text as it can.
Given Input: "<h1>Hello</h1>"
Expected Output: Extracted text: Hello
▼ Hint
- Match the literal opening tag, then capture the content, then match the literal closing tag.
- Use
(.*?)rather than(.*)for the captured content, so the match stops at the very first closing tag it finds. - This becomes important when a string contains more than one tag, since a greedy
.*would stretch across everything up through the very last closing tag instead.
▼ Solution & Explanation
Explanation:
<h1>and</h1>: Matched literally, since angle brackets are not special regex characters and need no escaping.(.*?): A reluctant quantifier that captures as few characters as possible while still allowing the rest of the pattern, the closing tag, to match.matcher.group(1): Retrieves just the captured content between the two tags, excluding the tags themselves.- Caution: This simple approach breaks down on nested or malformed HTML. For any real HTML parsing task, a dedicated HTML parser library is far more reliable than a hand-written regex.
Exercise 22: CamelCase Splitter
Problem Statement: Write a regex to split a CamelCase string into separate words (e.g., turn camelCaseString into camel Case String).
Purpose: This exercise introduces a zero-width split point built entirely from lookaround assertions, splitting on a position in the string rather than on any actual character.
Given Input: "camelCaseString"
Expected Output: camel Case String
▼ Hint
- Use a lookahead,
(?=[A-Z]), to find every position immediately before an uppercase letter. - Add a negative lookbehind,
(?<!^), to exclude the very first position in the string, since splitting there would produce an unwanted empty first element. - Pass the combined pattern to
String.split(), then join the resulting array with spaces.
▼ Solution & Explanation
Explanation:
(?=[A-Z]): A positive lookahead marking every position in the string that is immediately followed by an uppercase letter, without consuming that letter.(?<!^): A negative lookbehind excluding the very start of the string, so if the input itself began with an uppercase letter, that position would not trigger an extra split.input.split(regex): Splits the string at each qualifying zero-width position, without consuming or removing any actual characters from the result.String.join(" ", words): Reassembles the resulting word array into a single space-separated string for display.
Exercise 23: CSV Parser
Problem Statement: Match a line of comma-separated values, capturing each value while accounting for optional spaces around the commas.
Purpose: This exercise uses a regex as a splitting delimiter rather than a whole-string match, showing how split() can absorb surrounding whitespace along with the separator itself.
Given Input: "Java, Python , C++,Kotlin"
Expected Output:
[Java] [Python] [C++] [Kotlin]
▼ Hint
- Build a delimiter pattern that matches optional whitespace, then a comma, then more optional whitespace:
\s*,\s*. - Pass that pattern directly to
String.split()rather than splitting on a plain comma. - This approach naturally trims stray spaces around each value without a separate
trim()call on every element.
▼ Solution & Explanation
Explanation:
\\s*,\\s*: Matches zero or more whitespace characters, then a literal comma, then zero or more whitespace characters again, treating the whole thing as one delimiter.input.split(regex): Breaks the string apart everywhere that delimiter pattern occurs, discarding the matched commas and surrounding spaces entirely."Python "versus"Python": Without the surrounding\s*, splitting on a plain comma would leave a stray leading or trailing space on values like" Python ". The whitespace-aware delimiter avoids that entirely.- Square brackets in the output: Added purely for display, to make any accidental leftover whitespace in a value immediately visible around its printed brackets.
Exercise 24: Password Strength
Problem Statement: Validate a password that must be at least 8 characters long, contain at least one uppercase letter, one lowercase letter, one digit, and one special character.
Purpose: This exercise introduces stacking multiple lookaheads at the start of a pattern, a common technique for enforcing several independent conditions on the same string at once.
Given Input: testing "Passw0rd!", "password1", "PASSWORD1!", and "Pw1!"
Expected Output:
true false false false
▼ Hint
- Each requirement can be written as its own lookahead, like
(?=.*[A-Z])for “contains an uppercase letter somewhere”. - Since each lookahead is zero-width, they can all be stacked one after another at the very start of the pattern without consuming any characters.
- After all the lookaheads, add
.{8,}to actually consume and enforce the minimum overall length.
▼ Solution & Explanation
Explanation:
(?=.*[a-z]): Requires a lowercase letter somewhere in the string, checked without moving the matching position forward.(?=.*[A-Z])and(?=.*\\d): Similarly require an uppercase letter and a digit somewhere in the string, each as its own independent condition.(?=.*[^a-zA-Z0-9]): Requires at least one character that is not a letter or digit, covering punctuation and symbols as the “special character” requirement..{8,}: The only part of the pattern that actually consumes characters, requiring the whole string to be at least 8 characters long once every lookahead condition has already passed.
Exercise 25: Match Numbers NOT Followed by %
Problem Statement: Find all integers in a text that are not immediately followed by a percent sign (%).
Purpose: This exercise introduces negative lookahead for exclusion, and highlights a subtle pitfall: pairing it directly with a greedy quantifier can let the engine backtrack into a shorter, unintended partial match.
Given Input: "Sales grew 20% while profit rose 15 points and revenue hit 100"
Expected Output:
Found: 15 Found: 100
▼ Hint
- A first attempt like
\d+(?!%)looks reasonable, but the engine can backtrack\d+down to a shorter run of digits just to satisfy the lookahead, corrupting numbers like20%into a spurious partial match. - Add a second negative lookahead,
(?!\d), right after\d+, forcing the engine to only consider positions where the full run of digits has actually ended. - Only after confirming the digit run is complete should the
(?!%)check for a trailing percent sign be applied.
▼ Solution & Explanation
Explanation:
\\d+: Greedily matches as many consecutive digits as possible at the current position.(?!\\d): Confirms the digit run truly ended here, meaning the next character is not another digit. This blocks the engine from backtracking to a shorter, incomplete number just to satisfy a later check.(?!%): Only now checks whether a percent sign immediately follows the complete number, rejecting the whole match if it does.- Why
20is skipped entirely, not partially matched: Since(?!\d)forces the engine to only consider the complete two-digit run, and that complete run is immediately followed by%, the entire match attempt at that position fails, rather than falling back to matching just2.
Exercise 26: Match Words NOT Starting with ‘un’
Problem Statement: Find all words that do not begin with the prefix "un".
Purpose: This exercise applies a negative lookahead right after a word boundary, filtering out entire words based on how they start, rather than filtering individual characters.
Given Input: "The unhappy user was unable to undo the untimely mistake"
Expected Output:
The user was to the mistake
▼ Hint
- Start with
\bto anchor the check to the beginning of each word. - Add
(?!un)immediately after the boundary, rejecting any position where the next two characters areufollowed byn. - Follow that with
\w+\bto actually capture the rest of the word once the lookahead has passed.
▼ Solution & Explanation
Explanation:
\\b: Anchors the check to the start of a word, so the lookahead that follows only evaluates the first two characters of each word, not two characters found mid-word.(?!un): Rejects the position entirely if the next two characters spellun, ruling outunhappy,unable,undo, anduntimelyright at their starting boundary.\\w+\\b: Once a word survives the lookahead, this captures the rest of it, up through its own trailing word boundary.- Words like
"user": Pass the lookahead cleanly, since their first two characters areus, notun, even though the word starts with the same letteruthe excluded words do.
Exercise 27: Floating Point Numbers
Problem Statement: Match valid Java floating-point literals, including signs, decimals, and scientific notation (e.g., 3.14, -0.5, 6.022e23).
Purpose: This exercise combines optional signs, an optional decimal portion, and an entirely optional scientific notation suffix into one pattern, each piece marked with its own ? or wrapped in an optional group.
Given Input: testing "3.14", "-0.5", "6.022e23", and "abc"
Expected Output:
true true true false
▼ Hint
- Start with an optional sign,
[-+]?, since a number may or may not have one. - Allow an optional whole-number part, an optional decimal point, and a required set of digits, since some valid numbers start directly with a dot.
- Wrap the entire scientific notation suffix, the
eorE, an optional sign, and its digits, in one group followed by?, since a number is still valid without it.
▼ Solution & Explanation
Explanation:
[-+]?: Matches an optional leading sign, covering both positive and negative numbers, as well as numbers written with no sign at all.\\d*\\.?\\d+: Matches an optional whole-number part, an optional decimal point, and a required final group of digits, covering both3.14and simpler whole numbers without a decimal point.([eE][-+]?\\d+)?: An entirely optional group for scientific notation, matching aneorE, an optional sign, and one or more digits for the exponent."6.022e23".matches(regex): Returnstrue, since the base number6.022is followed by a valid scientific notation suffix,e23, matching the optional final group.
Exercise 28: Remove Multi-line Comments
Problem Statement: Write a regex to find and remove Java multi-line comments (/* ... */).
Purpose: This exercise introduces the DOTALL mode, needed because the default behavior of . does not match newline characters, which would otherwise prevent matching a comment that spans multiple lines.
Given Input: "int x = 5; /* this is a\nmulti-line comment */ int y = 10;"
Expected Output: int x = 5; int y = 10;
▼ Hint
- The literal
/*and*/both contain a regex-special character, the asterisk, which must be escaped as\*. - Use a reluctant
.*?between the two delimiters, so the match stops at the first*/rather than the last one in the string. - Add the inline flag
(?s)at the start of the pattern so.also matches newline characters, letting the comment span multiple lines.
▼ Solution & Explanation
Explanation:
(?s): An inline flag enablingDOTALLmode, so the.in the pattern that follows also matches newline characters, not just ordinary characters./\\*.*?\\*/: Matches a literal/*, then reluctantly consumes everything up to the first*/it encounters, correctly handling the embedded newline in the middle of the comment.code.replaceAll(regex, ""): Removes the entire matched comment, including its delimiters, replacing it with nothing.- Leftover double space: The final
.trim()only removes whitespace from the very start and end of the whole string. It does not collapse the double space left behind in the middle where the comment used to sit.
Exercise 29: Extract Email Components
Problem Statement: Validate an email address and split it into three distinct capturing groups: username, domain, and TLD (Top-Level Domain).
Purpose: This exercise combines several character classes with three separate capturing groups, extracting a structured breakdown out of a single validated string in one pass.
Given Input: "john.doe@example.com"
Expected Output:
Username: john.doe Domain: example TLD: com
▼ Hint
- Capture the username portion before the
@with a character class allowing letters, digits, dots, and a few common symbols. - Capture the domain portion after the
@but before the final dot separately from the TLD. - Require the TLD to be at least 2 letters, since real top-level domains like
comororgare never shorter than that.
▼ Solution & Explanation
Explanation:
([a-zA-Z0-9._%+-]+): The first capturing group, matching the username portion before the@symbol, allowing letters, digits, and a handful of common punctuation characters.([a-zA-Z0-9.-]+): The second capturing group, matching the domain portion, up to but not including the final dot before the TLD.\\.([a-zA-Z]{2,}): A literal dot followed by the third capturing group, requiring at least 2 letters for the TLD.matcher.group(1),group(2),group(3): Retrieve the three captured pieces independently, givingjohn.doe,example, andcomas three separate values from one single validated match.
Exercise 30: Thousands Separator
Problem Statement: Match positions in a long number string to insert commas as thousands separators (e.g., changing 1000000 to 1,000,000 using replaceAll).
Purpose: This closing exercise combines a positive lookbehind and a positive lookahead into a single zero-width insertion point, letting replaceAll() insert text at repeating positions without consuming or removing any digits.
Given Input: "1000000"
Expected Output: 1,000,000
▼ Hint
- Use a positive lookbehind,
(?<=\d), to require that a digit exists immediately before the insertion point. - Follow it with a positive lookahead requiring the remaining characters to consist of complete groups of exactly 3 digits, all the way to the end of the string.
- Since both parts are zero-width assertions,
replaceAll()can insert a comma at each matching position without deleting or duplicating any of the original digits.
▼ Solution & Explanation
Explanation:
(?<=\\d): A positive lookbehind requiring a digit immediately before the current position, ensuring commas are never inserted right at the very start of the number.(?=(\\d{3})+(?!\\d)): A positive lookahead requiring that everything remaining from this position to the end of the string forms one or more complete groups of exactly 3 digits, with the inner(?!\d)confirming there is no leftover partial group afterward.- Zero-width match: Neither lookaround consumes any characters, so this pattern only ever matches an empty-width position, which
replaceAll()then fills with a literal comma. "1000000"becoming"1,000,000": Commas are inserted after the 1st and 4th digits, since those are exactly the positions where a digit precedes them and the remaining digits still form complete groups of 3.

Leave a Reply