tessl install tessl/maven-io-cucumber--cucumber-expressions@19.0.0Cucumber Expressions are simple patterns for matching Step Definitions with Gherkin steps
The expression generation system automatically creates Cucumber Expressions from example text, suggesting appropriate parameter types based on the content. This is particularly useful for generating step definitions and snippets.
Generate cucumber expressions from example text with parameter type suggestions.
package io.cucumber.cucumberexpressions;
import java.util.List;
/**
* Generate cucumber expressions from example text
* Thread-safe after construction with configured registry
*/
public final class CucumberExpressionGenerator {
/**
* Create generator with parameter type registry
* The registry should be fully configured before creating the generator
* @param parameterTypeRegistry - Registry containing available parameter types
*/
public CucumberExpressionGenerator(ParameterTypeRegistry parameterTypeRegistry);
/**
* Generate candidate expressions from text
* Returns multiple expressions with different parameter type combinations
* Thread-safe operation - safe for concurrent calls
*
* The generated expressions are ordered by specificity:
* - More specific types (e.g., {int}) rank higher than generic types (e.g., {word})
* - Custom types with narrow patterns rank higher
* - Anonymous {} parameter is least specific
*
* @param text - Example text to generate expressions from
* @return List of generated expressions, ordered by specificity (most specific first)
*/
public List<GeneratedExpression> generateExpressions(String text);
}Usage Examples:
import io.cucumber.cucumberexpressions.*;
import java.util.Locale;
import java.util.List;
// Create generator
ParameterTypeRegistry registry = new ParameterTypeRegistry(Locale.ENGLISH);
CucumberExpressionGenerator generator = new CucumberExpressionGenerator(registry);
// Generate expressions from example text
List<GeneratedExpression> expressions = generator.generateExpressions(
"I have 42 cucumbers in my belly"
);
// Multiple candidate expressions are returned
for (GeneratedExpression expr : expressions) {
System.out.println(expr.getSource());
System.out.println("Parameter types: " + expr.getParameterTypes());
}
// Output might include:
// "I have {int} cucumbers in my belly" - [int]
// "I have {float} cucumbers in my belly" - [float]
// "I have {word} cucumbers in my belly" - [word]With Custom Parameter Types:
// Register custom parameter types
ParameterType<Color> colorType = new ParameterType<>(
"color",
"red|blue|green|yellow",
Color.class,
(String s) -> new Color(s)
);
registry.defineParameterType(colorType);
CucumberExpressionGenerator generator = new CucumberExpressionGenerator(registry);
// Generate expressions that recognize custom types
List<GeneratedExpression> expressions = generator.generateExpressions(
"I have a red ball and a blue box"
);
// Will include:
// "I have a {color} ball and a {color} box"
// "I have a {word} ball and a {word} box"Represents a generated expression with metadata about parameter types and names.
package io.cucumber.cucumberexpressions;
import java.util.List;
/**
* Generated expression with metadata
* Immutable value object - thread-safe
*/
public final class GeneratedExpression {
/**
* Get the expression source string
* This is the Cucumber Expression with parameter type placeholders
* @return Expression source (e.g., "I have {int} cucumbers")
*/
public String getSource();
/**
* Get generated parameter names for the expression
* Names are derived from parameter types and context
* Suitable for use as method parameter names in step definitions
*
* Examples:
* - {int} generates "int1", "int2" for multiple occurrences
* - {color} generates "color", "color2" for multiple occurrences
*
* @return List of parameter names suitable for method parameters
*/
public List<String> getParameterNames();
/**
* Get parameter types used in the expression
* @return List of parameter types in order of appearance
*/
public List<ParameterType<?>> getParameterTypes();
}Usage Examples:
import io.cucumber.cucumberexpressions.*;
import java.util.Locale;
import java.util.List;
ParameterTypeRegistry registry = new ParameterTypeRegistry(Locale.ENGLISH);
CucumberExpressionGenerator generator = new CucumberExpressionGenerator(registry);
List<GeneratedExpression> expressions = generator.generateExpressions(
"I have 42 cucumbers in my belly"
);
GeneratedExpression expr = expressions.get(0);
// Get the expression source
String source = expr.getSource();
// "I have {int} cucumbers in my belly"
// Get parameter names (for code generation)
List<String> paramNames = expr.getParameterNames();
// ["int1"] or similar generated name
// Get parameter types
List<ParameterType<?>> paramTypes = expr.getParameterTypes();
// [ParameterType<Integer>(name="int", type=Integer.class)]
// Use for snippet generation
System.out.println("@Given(\"" + source + "\")");
System.out.println("public void step(" +
paramTypes.get(0).getType().getSimpleName() + " " +
paramNames.get(0) + ") {");
System.out.println(" // Implementation");
System.out.println("}");
// Output:
// @Given("I have {int} cucumbers in my belly")
// public void step(Integer int1) {
// // Implementation
// }Multiple Parameters:
List<GeneratedExpression> expressions = generator.generateExpressions(
"Transfer 100 from account 12345 to account 67890"
);
GeneratedExpression expr = expressions.get(0);
String source = expr.getSource();
// "Transfer {int} from account {int} to account {int}"
List<String> paramNames = expr.getParameterNames();
// ["int1", "int2", "int3"]
List<ParameterType<?>> paramTypes = expr.getParameterTypes();
// [ParameterType<Integer>, ParameterType<Integer>, ParameterType<Integer>]The generator analyzes text to identify potential parameters:
Specificity Rules:
{int} rank higher than generic {word}{} parameter is least specificParameterTypeRegistry registry = new ParameterTypeRegistry(Locale.ENGLISH);
// Register specific custom type
ParameterType<Currency> currencyType = new ParameterType<>(
"currency",
"USD|EUR|GBP",
Currency.class,
(String s) -> Currency.valueOf(s)
);
registry.defineParameterType(currencyType);
CucumberExpressionGenerator generator = new CucumberExpressionGenerator(registry);
List<GeneratedExpression> expressions = generator.generateExpressions(
"Price is 100 USD"
);
// Results ordered by specificity:
// 1. "Price is {int} {currency}" - most specific
// 2. "Price is {int} {word}"
// 3. "Price is {float} {currency}"
// 4. "Price is {float} {word}"The primary use case for expression generation is creating step definition snippets:
import io.cucumber.cucumberexpressions.*;
import java.util.Locale;
import java.util.List;
public class SnippetGenerator {
public static String generateStepDefinition(String stepText) {
ParameterTypeRegistry registry = new ParameterTypeRegistry(Locale.ENGLISH);
CucumberExpressionGenerator generator = new CucumberExpressionGenerator(registry);
List<GeneratedExpression> expressions = generator.generateExpressions(stepText);
if (expressions.isEmpty()) {
return "// Could not generate expression for: " + stepText;
}
GeneratedExpression expr = expressions.get(0); // Use most specific
StringBuilder snippet = new StringBuilder();
snippet.append("@Given(\"").append(expr.getSource()).append("\")\n");
snippet.append("public void step(");
List<String> paramNames = expr.getParameterNames();
List<ParameterType<?>> paramTypes = expr.getParameterTypes();
for (int i = 0; i < paramNames.size(); i++) {
if (i > 0) snippet.append(", ");
String typeName = paramTypes.get(i).getType().getTypeName();
snippet.append(typeName).append(" ").append(paramNames.get(i));
}
snippet.append(") {\n");
snippet.append(" // TODO: implement step\n");
snippet.append(" throw new PendingException();\n");
snippet.append("}\n");
return snippet.toString();
}
public static void main(String[] args) {
String snippet = generateStepDefinition(
"I transfer 100 dollars from account 12345 to account 67890"
);
System.out.println(snippet);
}
}
// Output:
// @Given("I transfer {int} dollars from account {int} to account {int}")
// public void step(java.lang.Integer int1, java.lang.Integer int2, java.lang.Integer int3) {
// // TODO: implement step
// throw new PendingException();
// }Use useForSnippets parameter when defining custom types to control their inclusion in generated expressions:
// This type WILL appear in generated expressions
ParameterType<Priority> priorityType = new ParameterType<>(
"priority",
"low|medium|high",
Priority.class,
(String s) -> Priority.valueOf(s.toUpperCase()),
true, // useForSnippets = true
false
);
// This type WON'T appear in generated expressions
ParameterType<InternalId> internalType = new ParameterType<>(
"internal",
"[A-Z]{3}\\d{6}",
InternalId.class,
(String s) -> new InternalId(s),
false, // useForSnippets = false
false
);
registry.defineParameterType(priorityType);
registry.defineParameterType(internalType);
CucumberExpressionGenerator generator = new CucumberExpressionGenerator(registry);
// Generate from text with priority
List<GeneratedExpression> expr1 = generator.generateExpressions("Task is high priority");
// Will suggest: "Task is {priority} priority"
// Generate from text with internal ID
List<GeneratedExpression> expr2 = generator.generateExpressions("ID is ABC123456");
// Will NOT suggest: "ID is {internal}"
// Will suggest: "ID is {word}" instead// Empty text
List<GeneratedExpression> empty = generator.generateExpressions("");
// Returns list with one expression: ""
// Text with no parameters
List<GeneratedExpression> noParams = generator.generateExpressions("Hello world");
// Returns list with one expression: "Hello world"
// Text with only parameters
List<GeneratedExpression> onlyParams = generator.generateExpressions("42");
// Returns expressions: ["{int}", "{float}", "{word}"]
// Quoted strings
List<GeneratedExpression> quoted = generator.generateExpressions("Name is \"John Doe\"");
// Returns: "Name is {string}" (recognizes quoted string parameter type)The generator uses all parameter types registered in the registry:
ParameterTypeRegistry registry = new ParameterTypeRegistry(Locale.ENGLISH);
// Built-in types are already registered: int, float, string, word, etc.
// Add custom types for your domain
ParameterType<Email> emailType = new ParameterType<>(
"email",
"[\\w.]+@[\\w.]+",
Email.class,
(String s) -> new Email(s)
);
registry.defineParameterType(emailType);
ParameterType<PhoneNumber> phoneType = new ParameterType<>(
"phone",
"\\d{3}-\\d{3}-\\d{4}",
PhoneNumber.class,
(String s) -> new PhoneNumber(s)
);
registry.defineParameterType(phoneType);
// Generator now recognizes these patterns
CucumberExpressionGenerator generator = new CucumberExpressionGenerator(registry);
List<GeneratedExpression> expr = generator.generateExpressions(
"Contact user@example.com at 555-123-4567"
);
// Suggests: "Contact {email} at {phone}"Generation Performance:
Optimization Tips:
// GOOD: Reuse generator instance
CucumberExpressionGenerator generator = new CucumberExpressionGenerator(registry);
for (String text : manyTexts) {
List<GeneratedExpression> expressions = generator.generateExpressions(text);
// Process expressions...
}
// BAD: Creating generator for each text
for (String text : manyTexts) {
CucumberExpressionGenerator generator = new CucumberExpressionGenerator(registry);
List<GeneratedExpression> expressions = generator.generateExpressions(text);
// Wasteful recreation
}
// GOOD: Cache generated expressions if generation is frequent
Map<String, List<GeneratedExpression>> cache = new ConcurrentHashMap<>();
List<GeneratedExpression> cached = cache.computeIfAbsent(
text,
generator::generateExpressions
);Pattern 1: Custom Snippet Generator
public class CustomSnippetGenerator {
private final CucumberExpressionGenerator generator;
private final String language;
public CustomSnippetGenerator(ParameterTypeRegistry registry, String language) {
this.generator = new CucumberExpressionGenerator(registry);
this.language = language;
}
public String generateSnippet(String stepText, String annotation) {
List<GeneratedExpression> expressions = generator.generateExpressions(stepText);
if (expressions.isEmpty()) {
return "// Unable to generate snippet";
}
GeneratedExpression expr = expressions.get(0);
switch (language) {
case "java":
return generateJavaSnippet(expr, annotation);
case "kotlin":
return generateKotlinSnippet(expr, annotation);
case "scala":
return generateScalaSnippet(expr, annotation);
default:
throw new IllegalArgumentException("Unsupported language: " + language);
}
}
private String generateJavaSnippet(GeneratedExpression expr, String annotation) {
StringBuilder sb = new StringBuilder();
sb.append("@").append(annotation).append("(\"").append(expr.getSource()).append("\")\n");
sb.append("public void step(");
List<String> paramNames = expr.getParameterNames();
List<ParameterType<?>> paramTypes = expr.getParameterTypes();
for (int i = 0; i < paramNames.size(); i++) {
if (i > 0) sb.append(", ");
sb.append(paramTypes.get(i).getType().getTypeName())
.append(" ")
.append(paramNames.get(i));
}
sb.append(") {\n");
sb.append(" // TODO\n");
sb.append("}\n");
return sb.toString();
}
private String generateKotlinSnippet(GeneratedExpression expr, String annotation) {
// Implementation for Kotlin...
return "";
}
private String generateScalaSnippet(GeneratedExpression expr, String annotation) {
// Implementation for Scala...
return "";
}
}Pattern 2: Expression Suggestion System
public class ExpressionSuggester {
private final CucumberExpressionGenerator generator;
public ExpressionSuggester(ParameterTypeRegistry registry) {
this.generator = new CucumberExpressionGenerator(registry);
}
public List<Suggestion> getSuggestions(String text, int maxSuggestions) {
List<GeneratedExpression> expressions = generator.generateExpressions(text);
return expressions.stream()
.limit(maxSuggestions)
.map(expr -> new Suggestion(
expr.getSource(),
calculateScore(expr),
expr.getParameterTypes().size()
))
.sorted(Comparator.comparingDouble(Suggestion::getScore).reversed())
.collect(Collectors.toList());
}
private double calculateScore(GeneratedExpression expr) {
// Score based on specificity and number of parameters
double score = 1.0;
for (ParameterType<?> type : expr.getParameterTypes()) {
if (type.getName().equals("int") || type.getName().equals("float")) {
score += 0.5; // Numeric types are more specific
} else if (type.getName().equals("word")) {
score += 0.1; // Word is generic
} else {
score += 0.7; // Custom types are specific
}
}
return score;
}
public static class Suggestion {
private final String expression;
private final double score;
private final int parameterCount;
public Suggestion(String expression, double score, int parameterCount) {
this.expression = expression;
this.score = score;
this.parameterCount = parameterCount;
}
public String getExpression() { return expression; }
public double getScore() { return score; }
public int getParameterCount() { return parameterCount; }
}
}