tessl install tessl/maven-io-cucumber--cucumber-expressions@19.0.0Cucumber Expressions are simple patterns for matching Step Definitions with Gherkin steps
The parameter type system provides extensible type definitions for automatic conversion of matched strings to Java objects. The library includes comprehensive built-in types and supports custom parameter types with localized number formatting.
defineParameterType and setDefaultParameterTransformer calls before concurrent use. Safe for concurrent reads after configuration is complete.Defines custom parameter types for expression matching with automatic type conversion.
package io.cucumber.cucumberexpressions;
import java.lang.reflect.Type;
import java.util.List;
/**
* Define custom parameter types for expression matching
* Instances are immutable and thread-safe after construction
*/
public final class ParameterType<T> implements Comparable<ParameterType<?>> {
// Constructors with CaptureGroupTransformer (for multiple capture groups)
/**
* Full constructor with all options and CaptureGroupTransformer
* @param name - Parameter type name (used as {name} in expressions)
* @param regexps - List of regex patterns that match this type
* @param type - Java type to convert to
* @param transformer - Function to transform captured strings to type T
* @param useForSnippets - Whether to use in snippet generation (default: true)
* @param preferForRegexpMatch - Whether to prefer in regexp matching (default: false)
* @param useRegexpMatchAsStrongTypeHint - Whether regexp match provides strong type hint (default: true)
*/
public ParameterType(String name, List<String> regexps, Type type,
CaptureGroupTransformer<T> transformer,
boolean useForSnippets,
boolean preferForRegexpMatch,
boolean useRegexpMatchAsStrongTypeHint);
/**
* Constructor without useRegexpMatchAsStrongTypeHint (defaults to true)
*/
public ParameterType(String name, List<String> regexps, Type type,
CaptureGroupTransformer<T> transformer,
boolean useForSnippets,
boolean preferForRegexpMatch);
/**
* Constructor with Class instead of Type
*/
public ParameterType(String name, List<String> regexps, Class<T> type,
CaptureGroupTransformer<T> transformer,
boolean useForSnippets,
boolean preferForRegexpMatch);
/**
* Constructor with single regexp string instead of list
*/
public ParameterType(String name, String regexp, Class<T> type,
CaptureGroupTransformer<T> transformer,
boolean useForSnippets,
boolean preferForRegexpMatch);
/**
* Simplified constructor with defaults (useForSnippets=true, preferForRegexpMatch=false)
*/
public ParameterType(String name, List<String> regexps, Class<T> type,
CaptureGroupTransformer<T> transformer);
/**
* Simplified constructor with single regexp and defaults
*/
public ParameterType(String name, String regexp, Class<T> type,
CaptureGroupTransformer<T> transformer);
// Constructors with Transformer (for single capture group)
/**
* Full constructor with all options and Transformer
* @param name - Parameter type name (used as {name} in expressions)
* @param regexps - List of regex patterns that match this type
* @param type - Java type to convert to
* @param transformer - Function to transform captured string to type T
* @param useForSnippets - Whether to use in snippet generation
* @param preferForRegexpMatch - Whether to prefer in regexp matching
* @param useRegexpMatchAsStrongTypeHint - Whether regexp match provides strong type hint
*/
public ParameterType(String name, List<String> regexps, Type type,
Transformer<T> transformer,
boolean useForSnippets,
boolean preferForRegexpMatch,
boolean useRegexpMatchAsStrongTypeHint);
/**
* Constructor without useRegexpMatchAsStrongTypeHint (defaults to true)
*/
public ParameterType(String name, List<String> regexps, Type type,
Transformer<T> transformer,
boolean useForSnippets,
boolean preferForRegexpMatch);
/**
* Constructor with Class instead of Type
*/
public ParameterType(String name, List<String> regexps, Class<T> type,
Transformer<T> transformer,
boolean useForSnippets,
boolean preferForRegexpMatch,
boolean useRegexpMatchAsStrongTypeHint);
/**
* Constructor with Class, no useRegexpMatchAsStrongTypeHint
*/
public ParameterType(String name, List<String> regexps, Class<T> type,
Transformer<T> transformer,
boolean useForSnippets,
boolean preferForRegexpMatch);
/**
* Constructor with single regexp string
*/
public ParameterType(String name, String regexp, Class<T> type,
Transformer<T> transformer,
boolean useForSnippets,
boolean preferForRegexpMatch,
boolean useRegexpMatchAsStrongTypeHint);
/**
* Constructor with single regexp, no useRegexpMatchAsStrongTypeHint
*/
public ParameterType(String name, String regexp, Class<T> type,
Transformer<T> transformer,
boolean useForSnippets,
boolean preferForRegexpMatch);
/**
* Simplified constructor with defaults (useForSnippets=true, preferForRegexpMatch=false)
*/
public ParameterType(String name, List<String> regexps, Class<T> type,
Transformer<T> transformer);
/**
* Simplified constructor with single regexp and defaults
* This is the most commonly used constructor
*/
public ParameterType(String name, String regexp, Class<T> type,
Transformer<T> transformer);
// Static factory methods
/**
* Create a parameter type from an enum class
* Name will be the enum's simple class name (case-sensitive)
* Regex will match any enum constant name (exact match required)
* @param enumClass - Enum class to create parameter type from
* @return ParameterType for the enum
*/
public static <E extends Enum<E>> ParameterType<E> fromEnum(Class<E> enumClass);
// Accessor methods
/**
* Get the parameter type name (used as {name} in expressions)
* @return Parameter type name
*/
public String getName();
/**
* Get the Java type this parameter converts to
* @return Java Type
*/
public Type getType();
/**
* Get the regex patterns that match this type
* @return List of regex patterns (never empty)
*/
public List<String> getRegexps();
/**
* Check if this type is preferred for regexp matching
* When multiple types match in regex, preferred type is chosen
* @return true if preferential
*/
public boolean preferForRegexpMatch();
/**
* Check if this type is used for snippet generation
* When false, this type won't appear in generated expressions
* @return true if used for snippets
*/
public boolean useForSnippets();
/**
* Check if regexp match provides strong type hint
* Affects how transformers interpret type hints
* @return true if provides strong type hint
*/
public boolean useRegexpMatchAsStrongTypeHint();
/**
* Compare parameter types for sorting
* Used internally for match priority determination
* Integer type has highest weight (1000), others have weight 0
* @param that - Other parameter type
* @return Comparison result (negative, zero, or positive)
*/
public int compareTo(ParameterType<?> that);
/**
* Get weight for sorting (1000 for Integer type, 0 otherwise)
* Used internally for prioritizing type matches
* @return Weight value
*/
public int weight();
}Usage Examples:
import io.cucumber.cucumberexpressions.*;
import java.util.Arrays;
// Simple custom type with Transformer
ParameterType<Color> colorType = new ParameterType<>(
"color",
"red|blue|green|yellow",
Color.class,
(String s) -> new Color(s)
);
// Custom type with multiple regex patterns
ParameterType<Boolean> boolType = new ParameterType<>(
"boolean",
Arrays.asList("true|false", "yes|no", "on|off"),
Boolean.class,
(String s) -> s.matches("true|yes|on")
);
// Enum-based parameter type
enum Priority { LOW, MEDIUM, HIGH }
ParameterType<Priority> priorityType = ParameterType.fromEnum(Priority.class);
// Automatically matches "LOW", "MEDIUM", or "HIGH"
// Custom type with CaptureGroupTransformer (multiple captures)
ParameterType<Point> pointType = new ParameterType<>(
"point",
"(\\d+),(\\d+)",
Point.class,
(String[] args) -> new Point(Integer.parseInt(args[0]), Integer.parseInt(args[1]))
);
// Advanced: with all configuration options
ParameterType<Currency> currencyType = new ParameterType<>(
"currency",
Arrays.asList("USD|EUR|GBP", "\\$|€|£"),
Currency.class,
(String s) -> Currency.fromSymbol(s),
true, // useForSnippets - include in generated expressions
true, // preferForRegexpMatch - prefer when multiple types match
false // useRegexpMatchAsStrongTypeHint - weak type hint
);Registry for managing parameter types with built-in types pre-registered and locale-specific number formatting.
package io.cucumber.cucumberexpressions;
import java.util.Locale;
/**
* Registry for parameter types with built-in types pre-registered
* NOT thread-safe during modification. Safe for concurrent reads after full configuration.
*/
public final class ParameterTypeRegistry {
/**
* Create registry with locale-specific number formatting
* Includes all built-in parameter types:
* - int, byte, short, long (integer types)
* - float, double, bigdecimal, biginteger (decimal types)
* - word (non-whitespace string)
* - string (quoted string)
* - {} (anonymous parameter)
*
* @param locale - Locale for number parsing (affects decimal/thousands separators)
*/
public ParameterTypeRegistry(Locale locale);
/**
* Register a custom parameter type
* NOT thread-safe - complete all registrations before concurrent use
*
* @param parameterType - Parameter type to register
* @throws DuplicateTypeNameException if name already registered
* @throws CucumberExpressionException if preferForRegexpMatch conflicts with existing type
*/
public void defineParameterType(ParameterType<?> parameterType);
/**
* Set default transformer for anonymous parameters
* This transformer is used for {} anonymous parameters when no explicit type matches
* Also used as fallback when type hints are provided
* NOT thread-safe - set before concurrent use
*
* @param defaultParameterTransformer - Transformer to use for anonymous parameters
*/
public void setDefaultParameterTransformer(
ParameterByTypeTransformer defaultParameterTransformer);
}Built-in Parameter Types:
The registry includes these pre-registered types with locale-aware number parsing:
| Type | Syntax | Regex Pattern | Java Type | Example Match | Notes |
|---|---|---|---|---|---|
| Integer | {int} | -?\d+ | java.lang.Integer | 42, -19 | Supports thousands separators per locale |
| Byte | {byte} | -?\d+ | java.lang.Byte | 127, -128 | Range: -128 to 127 |
| Short | {short} | -?\d+ | java.lang.Short | 1000, -1000 | Range: -32768 to 32767 |
| Long | {long} | -?\d+ | java.lang.Long | 1234567890 | 64-bit integer |
| Float | {float} | Locale-specific | java.lang.Float | 3.6, -9.2, 1.23e5 | Supports scientific notation |
| Double | {double} | Locale-specific | java.lang.Double | 3.14159 | 64-bit floating point |
| BigInteger | {biginteger} | -?\d+ | java.math.BigInteger | 999999999999 | Arbitrary precision integer |
| BigDecimal | {bigdecimal} | Locale-specific | java.math.BigDecimal | 99.99 | Arbitrary precision decimal |
| Word | {word} | [^\\s]+ | java.lang.String | hello, user123 | No whitespace |
| String | {string} | "([^"]*)"|'([^']*)' | java.lang.String | "hello", 'world' | Quotes removed |
| Anonymous | {} | .* | java.lang.Object | Any text | Greedy match |
Number Format Regex Patterns:
Integer types (int, byte, short, long, biginteger):
-?\d+1,234 or 1.234)Float types (float, double, bigdecimal):
. or ,), or .)1.23e5, 1.23E-5Usage Examples:
import io.cucumber.cucumberexpressions.*;
import java.util.Locale;
// Create registry with English locale
ParameterTypeRegistry registry = new ParameterTypeRegistry(Locale.ENGLISH);
// Register custom parameter types
ParameterType<Color> colorType = new ParameterType<>(
"color",
"red|blue|green",
Color.class,
(String s) -> new Color(s)
);
registry.defineParameterType(colorType);
// Use in expressions
ExpressionFactory factory = new ExpressionFactory(registry);
Expression expr = factory.createExpression("I have a {color} ball");
Optional<List<Argument<?>>> match = expr.match("I have a red ball");
// match contains [Argument<Color>(Color.RED)]
// Set default transformer for anonymous parameters
registry.setDefaultParameterTransformer((fromValue, toValueType) -> {
if (toValueType == java.util.UUID.class) {
return java.util.UUID.fromString(fromValue);
}
// Fallback to default behavior
return fromValue;
});
// Use anonymous parameter with type hint
Expression anonymousExpr = factory.createExpression("ID is {}");
Optional<List<Argument<?>>> match2 = anonymousExpr.match(
"ID is 550e8400-e29b-41d4-a716-446655440000",
java.util.UUID.class // Type hint
);
// Uses default transformer to convert to UUIDNumber parameter types support locale-specific formatting:
import io.cucumber.cucumberexpressions.*;
import java.util.Locale;
// English locale: comma thousands separator, period decimal separator
ParameterTypeRegistry englishRegistry = new ParameterTypeRegistry(Locale.ENGLISH);
ExpressionFactory englishFactory = new ExpressionFactory(englishRegistry);
Expression englishExpr = englishFactory.createExpression("Price is {float}");
Optional<List<Argument<?>>> match1 = englishExpr.match("Price is 1,234.56");
Float value1 = (Float) match1.get().get(0).getValue();
// value1 = 1234.56f
// French locale: period thousands separator, comma decimal separator
ParameterTypeRegistry frenchRegistry = new ParameterTypeRegistry(Locale.FRENCH);
ExpressionFactory frenchFactory = new ExpressionFactory(frenchRegistry);
Expression frenchExpr = frenchFactory.createExpression("Prix est {float}");
Optional<List<Argument<?>>> match2 = frenchExpr.match("Prix est 1.234,56");
Float value2 = (Float) match2.get().get(0).getValue();
// value2 = 1234.56f
// German locale
ParameterTypeRegistry germanRegistry = new ParameterTypeRegistry(Locale.GERMAN);
ExpressionFactory germanFactory = new ExpressionFactory(germanRegistry);
Expression germanExpr = germanFactory.createExpression("Preis ist {float}");
Optional<List<Argument<?>>> match3 = germanExpr.match("Preis ist 1.234,56");
Float value3 = (Float) match3.get().get(0).getValue();
// value3 = 1234.56fKeyboard-Friendly Format Symbols:
The library uses keyboard-friendly symbols for better accessibility:
| Locale Feature | Standard | Keyboard-Friendly | Example |
|---|---|---|---|
| Minus sign | U+2212 (−) | - (hyphen-minus, ASCII 45) | -42 |
| English decimal | . | . (period) | 123.45 |
| English thousands | , | , (comma) | 1,234.56 |
| French decimal | , | , (comma) | 123,45 |
| French thousands | . | . (period) | 1.234,56 |
| Scientific notation | e or E | e or E | 1.23e5 |
Rules:
- (ASCII 45), not Unicode minus sign (U+2212)., thousands separator is comma ,,, thousands separator is period .useForSnippets:
Controls whether the parameter type appears in generated expressions:
true: The parameter type is used in snippet generationfalse: It won't be suggested in generated snippetstrue// This type won't appear in generated snippets
ParameterType<InternalId> internalIdType = new ParameterType<>(
"internalId",
"[A-Z]{3}\\d{6}",
InternalId.class,
(String s) -> new InternalId(s),
false, // useForSnippets = false (won't appear in generated expressions)
false
);
// When generating expressions from text like "ID is ABC123456",
// the generator will suggest {word} instead of {internalId}preferForRegexpMatch:
Controls precedence when multiple parameter types match in regular expressions:
true: This type is preferred when multiple types match the same regexpfalse: Standard matching precedence appliesfalseIMPORTANT CONSTRAINT: Only ONE parameter type per regexp pattern can have preferForRegexpMatch=true. Attempting to register multiple preferred types for the same regexp throws CucumberExpressionException.
// This type is preferred in regexp matching
ParameterType<SpecialNumber> specialNumType = new ParameterType<>(
"specialNum",
"\\d+",
SpecialNumber.class,
(String s) -> new SpecialNumber(s),
true,
true // preferForRegexpMatch = true
);
registry.defineParameterType(specialNumType);
// INCORRECT: Attempting to register another preferred type with same regexp
ParameterType<Integer> anotherNumType = new ParameterType<>(
"anotherNum",
"\\d+", // Same regexp as above!
Integer.class,
Integer::parseInt,
true,
true // preferForRegexpMatch = true - CONFLICT!
);
try {
registry.defineParameterType(anotherNumType);
// Throws: CucumberExpressionException
// "There can only be one preferential parameter type per regexp.
// The regexp /\d+/ is used for two preferential parameter types,
// {specialNum} and {anotherNum}"
} catch (CucumberExpressionException e) {
System.err.println("Cannot have multiple preferred types for same regexp: " + e.getMessage());
}
// CORRECT: Only one type has preferForRegexpMatch=true
ParameterType<Integer> normalNumType = new ParameterType<>(
"normalNum",
"\\d+",
Integer.class,
Integer::parseInt,
true,
false // preferForRegexpMatch = false - OK!
);
registry.defineParameterType(normalNumType);useRegexpMatchAsStrongTypeHint:
Controls type hint strength for transformers:
true: Regexp matches provide strong type hints for transformersfalse: Weaker type hint for more flexibilitytrue for custom types, false for built-in typesParameterType<CustomType> customType = new ParameterType<>(
"custom",
"pattern",
CustomType.class,
(String s) -> new CustomType(s),
true,
false,
true // useRegexpMatchAsStrongTypeHint = true
);The fromEnum factory method simplifies enum parameter type creation:
/**
* Create a parameter type from an enum class
* Name will be the enum's simple class name (case-sensitive)
* Regex will match any enum constant name (exact match required)
* @param enumClass - Enum class to create parameter type from
* @return ParameterType for the enum
*/
public static <E extends Enum<E>> ParameterType<E> fromEnum(Class<E> enumClass);Usage:
enum Status { PENDING, APPROVED, REJECTED }
// Automatically creates pattern: PENDING|APPROVED|REJECTED
ParameterType<Status> statusType = ParameterType.fromEnum(Status.class);
// Name: "Status" (from class name)
// Regex: "PENDING|APPROVED|REJECTED"
// Transformer: Enum.valueOf(Status.class, value)
ParameterTypeRegistry registry = new ParameterTypeRegistry(Locale.ENGLISH);
registry.defineParameterType(statusType);
ExpressionFactory factory = new ExpressionFactory(registry);
Expression expr = factory.createExpression("Request is {Status}");
Optional<List<Argument<?>>> match = expr.match("Request is APPROVED");
Status status = (Status) match.get().get(0).getValue();
// status = Status.APPROVED
// Case-sensitive matching
Optional<List<Argument<?>>> noMatch = expr.match("Request is approved");
// noMatch.isEmpty() = true (case mismatch)Enum with Custom Name:
// Use regular constructor for custom name
enum Priority { LOW, MEDIUM, HIGH }
ParameterType<Priority> priorityType = new ParameterType<>(
"priority", // Custom name (lowercase)
"LOW|MEDIUM|HIGH",
Priority.class,
Priority::valueOf
);
Expression expr = factory.createExpression("Task has {priority} priority");
Optional<List<Argument<?>>> match = expr.match("Task has HIGH priority");
// MatchesParameter types are comparable for sorting purposes:
/**
* Compare parameter types for sorting
* Used internally for match priority determination
* @param that - Other parameter type
* @return Comparison result
*/
public int compareTo(ParameterType<?> that);
/**
* Get weight for sorting (1000 for Integer type, 0 otherwise)
* @return Weight value
*/
public int weight();Sorting Rules:
ParameterType<Integer> intType = /* built-in int type */;
ParameterType<String> stringType = /* custom string type */;
intType.weight(); // Returns 1000
stringType.weight(); // Returns 0
intType.compareTo(stringType); // Returns positive (int has higher priority)DuplicateTypeNameException:
Thrown when registering a parameter type with an existing name:
ParameterTypeRegistry registry = new ParameterTypeRegistry(Locale.ENGLISH);
ParameterType<Color> colorType1 = new ParameterType<>(
"color", "red|blue", Color.class, (String s) -> new Color(s)
);
registry.defineParameterType(colorType1);
try {
ParameterType<String> colorType2 = new ParameterType<>(
"color", ".*", String.class, (String s) -> s // Duplicate name "color"
);
registry.defineParameterType(colorType2);
// Throws DuplicateTypeNameException
} catch (DuplicateTypeNameException e) {
System.err.println("Duplicate type name: " + e.getMessage());
// Handle duplicate name...
}CucumberExpressionException for Invalid Names:
Parameter type names cannot contain certain special characters:
// Valid names
new ParameterType<>("color", "...", String.class, s -> s);
new ParameterType<>("my_type", "...", String.class, s -> s);
new ParameterType<>("Type123", "...", String.class, s -> s);
new ParameterType<>("my-type", "...", String.class, s -> s);
// Invalid names (contain special characters)
try {
new ParameterType<>("{color}", "...", String.class, s -> s);
// Throws CucumberExpressionException
} catch (CucumberExpressionException e) {
// Handle invalid name
}
// Characters that must be avoided in names:
// {, }, (, ), /, \, whitespaceCucumberExpressionException for Conflicting Preferred Types:
ParameterType<Type1> type1 = new ParameterType<>(
"type1", "\\d+", Type1.class, Type1::new, true, true
);
registry.defineParameterType(type1);
ParameterType<Type2> type2 = new ParameterType<>(
"type2", "\\d+", Type2.class, Type2::new, true, true // Conflicts!
);
try {
registry.defineParameterType(type2);
// Throws CucumberExpressionException
} catch (CucumberExpressionException e) {
System.err.println("Conflicting preferred types: " + e.getMessage());
}Pattern 1: Type Hierarchy
// Base type for all IDs
abstract class Id {
protected final String value;
public Id(String value) { this.value = value; }
}
// Specific ID types
class UserId extends Id {
public UserId(String value) { super(value); }
}
class OrderId extends Id {
public OrderId(String value) { super(value); }
}
// Register specific types
ParameterType<UserId> userIdType = new ParameterType<>(
"userId",
"user-\\d+",
UserId.class,
UserId::new
);
ParameterType<OrderId> orderIdType = new ParameterType<>(
"orderId",
"order-\\d+",
OrderId.class,
OrderId::new
);
registry.defineParameterType(userIdType);
registry.defineParameterType(orderIdType);
// Use in expressions
Expression expr = factory.createExpression("User {userId} ordered {orderId}");
Optional<List<Argument<?>>> match = expr.match("User user-123 ordered order-456");
// Arguments: [UserId("user-123"), OrderId("order-456")]Pattern 2: Conditional Registration
public void registerIfNotExists(ParameterTypeRegistry registry, ParameterType<?> type) {
try {
registry.defineParameterType(type);
} catch (DuplicateTypeNameException e) {
// Already registered, skip
System.out.println("Type '" + type.getName() + "' already registered");
}
}Pattern 3: Domain-Specific Types
// Email type
ParameterType<Email> emailType = new ParameterType<>(
"email",
"[\\w.+-]+@[\\w.-]+\\.[a-z]{2,}",
Email.class,
Email::new
);
// URL type
ParameterType<URL> urlType = new ParameterType<>(
"url",
"https?://[\\w.-]+(?:/[\\w.-]*)*",
URL.class,
(String s) -> new URL(s)
);
// Phone number type
ParameterType<PhoneNumber> phoneType = new ParameterType<>(
"phone",
"\\+?[\\d\\s\\-()]+",
PhoneNumber.class,
PhoneNumber::parse
);
// Date type
ParameterType<LocalDate> dateType = new ParameterType<>(
"date",
"\\d{4}-\\d{2}-\\d{2}",
LocalDate.class,
LocalDate::parse
);
// Register all domain types
registry.defineParameterType(emailType);
registry.defineParameterType(urlType);
registry.defineParameterType(phoneType);
registry.defineParameterType(dateType);Pattern 4: Dynamic Type Registration
public class DynamicTypeRegistry {
private final ParameterTypeRegistry registry;
private final Map<String, ParameterType<?>> registeredTypes = new ConcurrentHashMap<>();
public DynamicTypeRegistry(Locale locale) {
this.registry = new ParameterTypeRegistry(locale);
}
public <T> void registerType(String name, String regexp, Class<T> type,
Transformer<T> transformer) {
if (registeredTypes.containsKey(name)) {
return; // Already registered
}
ParameterType<T> paramType = new ParameterType<>(name, regexp, type, transformer);
registry.defineParameterType(paramType);
registeredTypes.put(name, paramType);
}
public ParameterTypeRegistry getRegistry() {
return registry;
}
}Registration Cost:
Lookup Performance:
Memory Usage:
Optimization Tips:
// GOOD: Register once at startup
public class TypeRegistrar {
private static final ParameterTypeRegistry REGISTRY;
static {
REGISTRY = new ParameterTypeRegistry(Locale.ENGLISH);
// Register all types once
registerAllTypes(REGISTRY);
}
public static ParameterTypeRegistry getRegistry() {
return REGISTRY;
}
}
// BAD: Creating new registry repeatedly
for (int i = 0; i < 1000; i++) {
ParameterTypeRegistry registry = new ParameterTypeRegistry(Locale.ENGLISH);
// Wasteful recreation
}Empty Regex Pattern:
// Valid but matches empty string
ParameterType<Empty> emptyType = new ParameterType<>(
"empty",
"",
Empty.class,
(String s) -> new Empty()
);
// Use case: optional markers or flagsMultiple Regex Patterns:
// Type with multiple acceptable patterns
ParameterType<Boolean> boolType = new ParameterType<>(
"bool",
Arrays.asList("true|false", "yes|no", "1|0"),
Boolean.class,
(String s) -> s.matches("true|yes|1")
);
// Matches any of: true, false, yes, no, 1, 0Overlapping Patterns:
// Two types with overlapping but distinct patterns
ParameterType<Integer> intType = new ParameterType<>(
"int", "\\d+", Integer.class, Integer::parseInt
);
ParameterType<PositiveInt> positiveType = new ParameterType<>(
"positive", "\\d+", PositiveInt.class, PositiveInt::new,
false, // Don't use in snippets
true // Prefer in regex
);
// When used in Cucumber Expression, name determines type:
// {int} -> uses intType
// {positive} -> uses positiveType
// When used in regex, preferForRegexpMatch determines precedenceNull Transformer Results:
// Transformer that may return null
ParameterType<OptionalValue> optType = new ParameterType<>(
"optional",
".*",
OptionalValue.class,
(String s) -> s.isEmpty() ? null : new OptionalValue(s)
);
// Argument.getValue() may return null