The reason that you do not get an optional cat after a reluctantly-qualified .+? is that it is both optional and non-anchored: the engine is not forced to make that match, because it can legally treat the cat as the "tail" of the .+? sequence.

If you anchor the cat at the end of the string, i.e. use ^(dog).+?(cat)?$, you would get a match, though:

Pattern p = Pattern.compile("^(dog).+?(cat)?$");
for (String s : new String[] {"dog, cat", "dog, dog, cat", "dog, dog, dog"}) {
    Matcher m = p.matcher(s);
    if (m.find()) {
        System.out.println(m.group(1)+" "+m.group(2));
    }
}

This prints (demo 1)

dog cat
dog cat
dog null

Do you happen to know how to deal with it in case there's something after cat?

You can deal with it by constructing a trickier expression that matches anything except cat, like this:

^(dog)(?:[^c]|c[^a]|ca[^t])+(cat)?

Now the cat could happen anywhere in the string without an anchor (demo 2).

Answer from Sergey Kalinichenko on Stack Overflow
Top answer
1 of 4
43

The reason that you do not get an optional cat after a reluctantly-qualified .+? is that it is both optional and non-anchored: the engine is not forced to make that match, because it can legally treat the cat as the "tail" of the .+? sequence.

If you anchor the cat at the end of the string, i.e. use ^(dog).+?(cat)?$, you would get a match, though:

Pattern p = Pattern.compile("^(dog).+?(cat)?$");
for (String s : new String[] {"dog, cat", "dog, dog, cat", "dog, dog, dog"}) {
    Matcher m = p.matcher(s);
    if (m.find()) {
        System.out.println(m.group(1)+" "+m.group(2));
    }
}

This prints (demo 1)

dog cat
dog cat
dog null

Do you happen to know how to deal with it in case there's something after cat?

You can deal with it by constructing a trickier expression that matches anything except cat, like this:

^(dog)(?:[^c]|c[^a]|ca[^t])+(cat)?

Now the cat could happen anywhere in the string without an anchor (demo 2).

2 of 4
20

Without any particular order, other options to match such patterns are:

Method 1

With non-capturing groups:

^(?:dog(?:, |

RegEx Demo 1

Or with capturing groups:

^(dog(?:, |

RegEx Demo 2


Method 2

With lookarounds,

(?<=^|, )dog|cat(?=$|,)

RegEx Demo 3

With word boundaries,

(?<=^|, )\b(?:dog|cat)\b(?=$|,)

RegEx Demo 4


Method 3

If we would have had only one cat and no dog in the string, then

^(?:dog(?:, |

would have been an option too.

RegEx Demo 5

Test

import java.util.regex.Matcher;
import java.util.regex.Pattern;


public class RegularExpression{

    public static void main(String[] args){

        final String regex = "^(?:dog(?:, |";
        final String string = "cat\n"
             + "dog, cat\n"
             + "dog, dog, cat\n"
             + "dog, dog, dog\n"
             + "dog, dog, dog, cat\n"
             + "dog, dog, dog, dog, cat\n"
             + "dog, dog, dog, dog, dog\n"
             + "dog, dog, dog, dog, dog, cat\n"
             + "dog, dog, dog, dog, dog, dog, dog, cat\n"
             + "dog, dog, dog, dog, dog, dog, dog, dog, dog\n";

        final Pattern pattern = Pattern.compile(regex, Pattern.MULTILINE);
        final Matcher matcher = pattern.matcher(string);

        while (matcher.find()) {
            System.out.println("Full match: " + matcher.group(0));
            for (int i = 1; i <= matcher.groupCount(); i++) {
                System.out.println("Group " + i + ": " + matcher.group(i));
            }
        }

    }
}

Output

Full match: cat
Full match: dog, cat
Full match: dog, dog, cat
Full match: dog, dog, dog
Full match: dog, dog, dog, cat
Full match: dog, dog, dog, dog, cat
Full match: dog, dog, dog, dog, dog
Full match: dog, dog, dog, dog, dog, cat
Full match: dog, dog, dog, dog, dog, dog, dog, cat
Full match: dog, dog, dog, dog, dog, dog, dog, dog, dog

If you wish to simplify/modify/explore the expression, it's been explained on the top right panel of regex101.com. If you'd like, you can also watch in this link, how it would match against some sample inputs.


RegEx Circuit

jex.im visualizes regular expressions:

🌐
freeCodeCamp
freecodecamp.org › news › how-do-i-make-regex-optional-specify-optional-pattern-in-regular-expressions
How Do I Make RegEx Optional? Specify Optional Pattern in Regular Expressions
March 9, 2023 - The question zero or one (?) quantifier would also work fine in any language that supports RegEx. Here’s an example in JavaScript: let str1 = 'Color is American'; let str2 = 'Colour is British'; let re1 = /colou?r/i; console.log(re1.exec(str1)); // [ 'Color', index: 0, input: 'Color is American', groups: undefined ] console.log(re1.exec(str2)); // [ 'Colour', index: 0, input: 'Colour is British', groups: undefined ] The zero or one quantifier helps stop regular expressions from being greedy by making a string optional on its own or within others.
🌐
Reddit
reddit.com › r/regex › capture one group with an optional group inside
r/regex on Reddit: Capture one group with an optional group inside
February 3, 2021 -

I need to capture a group which contains a partial optional part inside, but I can't manage to build it.

Example: iphone or iphone11

I need to capture iphone (if it's only iPhone) or iphone11 if it has the 11 together. This is just an example, it isn't necessarily numbers.

Example 2: abcd or abcdef

I want to capture abcd or abcdef.

I was trying by using this:

(iphone(11)?) OR (abcd(ef)?)

But it obviously gives me 2 results if the second capturing group exists. And I need it as 1 result only.

It's more complex that simply putting simple alternatives like this:

(iphone|iphone11)

PCRE

Edit: clarifying

🌐
MDN Web Docs
developer.mozilla.org › en-US › docs › Web › JavaScript › Reference › Regular_expressions › Non-capturing_group
Non-capturing group: (?:...) - JavaScript | MDN
July 8, 2025 - It acts like the grouping operator ... useful capturing groups. ... A pattern consisting of anything you may use in a regex literal, including a disjunction. In the following example, we test if a file path ends with styles.css or styles.[a hex hash].css. Because the entire \.[\da-f]+ part is optional, in order ...
🌐
Reddit
reddit.com › r/regex › multiple optional capture groups of paragraphs between keywords
r/regex on Reddit: Multiple optional capture groups of paragraphs between keywords
January 17, 2021 -

I'm a complete beginner to Regex, and I'm basically only using it for 1 task in a program called Obsidian. It uses Javascript. This problem seems easy to me, but regex is a different beast. I've tried searching for answer for a few hours now, but I'm out of luck. Thank you for the help in advance—I really appreciate your time!

Here is my template essentially:

#card

sample text

Extra:

sample text

---

I would like to match 2 capture groups:

The first capture group should include everything (even whitespace) after the "#card" keyword and before the optional "Extra: " keyword. However, if "Extra: " is not present, it should include everything before the "---" keyword.

The second optional capture group should include everything after the "Extra: " keyword and before the "---" keyword. It only matches if it comes after "#card". Again, it's optional, so if "Extra: " is not present, it only returns the first capture group (if matched).

The multiline flag is applied by the plugin as well.

Here are some examples:

Examples that should match:

#card

(capture 1)

Extra:

(capture 2)

---

2.

#card

(capture 1)

---

3.

#card

(capture 1)

{white space}

---

Examples that shouldn't match:

Extra:

(anything)

---

2.

#card

(anything)

Extra:

3.

#random

(anything)

Extra:

(anything)

---

Thank you again for the help! Please let me know if you have any questions.

🌐
Coderanch
coderanch.com › t › 621404 › java › java-regex-capturing-groups-optional
java regex : capturing groups that are optional (Java in General forum at Coderanch)
October 7, 2013 - TS12: This is system code I need to extract three different string where 3rd group in input string is optional and I want tmy group to be returned 'null' in that case if possible. three components : TS This is system code SYSCODE (optional , I want to extract contents inside square brackets if present). I am reformatting these strings in my UI and need to know if 3rd group is present or not.Somehow I am not able to make it work. This is regex i am trying to modify to make it work.
Find elsewhere
🌐
JavaScript.info
javascript.info › tutorial › regular expressions
Capturing groups
January 27, 2024 - The slash / should be escaped inside a JavaScript regexp /.../, we’ll do that later. We need a number, an operator, and then another number. And optional spaces between them. The full regular expression: -?\d+(\.\d+)?\s*[-+*/]\s*-?\d+(\.\d+)?. ... To make each of these parts a separate element of the result array, let’s enclose them in parentheses: (-?\d+(\.\d+)?)\s*([-+*/])\s*(-?\d+(\.\d+)?). ... result[5] == undefined (fifth group (\.\d+)?
🌐
MDN Web Docs
developer.mozilla.org › en-US › docs › Web › JavaScript › Reference › Regular_expressions › Capturing_group
Capturing group: (...) - JavaScript | MDN
July 8, 2025 - You can optionally specify a name to a capturing group, which helps avoid pitfalls related to group positions and indexing. See Named capturing groups for more information. Parentheses have other purposes in different regex syntaxes. For example, they also enclose lookahead and lookbehind ...
🌐
Regular-Expressions.info
regular-expressions.info › optional.html
Regex Tutorial: The Question Mark Makes the Preceding Token Optional
You can make several tokens optional by grouping them together using parentheses, and placing the question mark after the closing parenthesis. E.g.: Nov(ember)?
🌐
Redbitdev
redbitdev.com › post › capture-groups-in-regular-expressions
Capture Groups in Regular Expressions
Consider the following regular ... around the '/options' subpath: '(\/options\/(?<optionId>\d+))?'. The question mark after the closing parenthesis makes the capture group optional, which means that this regex will match ...
🌐
Rexegg
rexegg.com › regex-capture.php
Regex Capture Groups and Back-References
For instance, you might try to write this to set the PriorError group at various places in the pattern, much like a variable or a flag: (?x) # free-spacing mode # this regex attempts to match "dog", # allowing for a one-character error, e.g. dig or bog, but not bug d? # "d" is the first character. It is optional.
🌐
PerlMonks
perlmonks.org
problem with optional capture group
Special_K has asked for the wisdom of the Perl Monks concerning the following question: · I am trying to use a regex to match lines that will always have an opening <div tag and could optionally have a closing </div tag on the same line. If the closing </div tag is present, additional code ...
🌐
Python Pool
pythonpool.com › home › blog › the secret guide to regex optional group
The Secret Guide To Regex Optional Group - Python Pool
January 22, 2022 - To do that, we will add “?=” at the start of the group. In the above example, we did that by adding a lookahead/ non-capturing group for “https://” and then making it optional, which returns all the strings accordingly.
🌐
Study Plan
studyplan.dev › pro-cpp › regex-capture-groups
Regular Expression (Regex) Capture Groups in C++ | A Practical Guide
October 7, 2025 - When we want to look for literal ( and ) in our strings rather than creating a capture group, we can escape them in the usual way, using \. For example, if we want to search a string for the sequence "The (big) cat", our regex would be "The \(big\) cat"
🌐
Microsoft Learn
learn.microsoft.com › en-us › dotnet › standard › base-types › grouping-constructs-in-regular-expressions
Grouping Constructs in Regular Expressions - .NET | Microsoft Learn
The following grouping construct captures a matched subexpression and lets you access it by name or by number: ... Here, name is a valid group name, and subexpression is any valid regular expression pattern. name must not contain any punctuation characters and cannot begin with a number. ... If the RegexOptions parameter of a regular expression pattern matching method includes the RegexOptions.ExplicitCapture flag, or if the n option is applied to this subexpression (see Group options later in this topic), the only way to capture a subexpression is to explicitly name capturing groups.
🌐
RegExr
regexr.com
RegExr: Learn, Build, & Test RegEx
Regular expression tester with syntax highlighting, PHP / PCRE & JS Support, contextual help, cheat sheet, reference, and searchable community patterns.
🌐
Splunk Community
community.splunk.com › t5 › Splunk-Search › Regex-optional-groups › m-p › 173689
Solved: Regex optional groups - Splunk Community
March 11, 2015 - Hi guys, i'm trying to get this (simplified) regex running (for several days now): ^(?P .+)(?:\s*SIP/2.0\s+(?P .+))? i also tried another flavour of this statement: ^(?P .+)(?:\s*SIP/2.0\s+(?P .+)|\w*) With "Extract Fields" i tested this regex on splunkweb and it...