Cucumber-JVM Java library providing annotation-based step definitions for Behavior-Driven Development (BDD) testing
—
Custom parameter types for converting step parameters from strings to domain objects, with extensive configuration options. Cucumber-Java provides a sophisticated transformation system including custom parameter types, default transformers, and data table transformations.
Register custom parameter types to convert strings from step definitions into domain objects using regular expressions.
/**
* Register custom parameter types for Cucumber expressions
* @param value Regular expression pattern for matching
* @param name Parameter type name (default: method name)
* @param preferForRegexMatch Preferential type for regex matching (default: false)
* @param useForSnippets Use in snippet generation (default: false)
* @param useRegexpMatchAsStrongTypeHint Provide strong type hint (default: false)
*/
@ParameterType(value = "regex",
name = "typeName",
preferForRegexMatch = false,
useForSnippets = false,
useRegexpMatchAsStrongTypeHint = false)
public CustomType parameterTransformer(String input) {
return new CustomType(input);
}
/**
* Multi-capture group parameter type
*/
@ParameterType("(\\d+)-(\\d+)-(\\d+)")
public LocalDate date(String year, String month, String day) {
return LocalDate.of(Integer.parseInt(year),
Integer.parseInt(month),
Integer.parseInt(day));
}Usage Examples:
import io.cucumber.java.ParameterType;
import java.time.LocalDate;
import java.time.format.DateTimeFormatter;
public class ParameterTypes {
// Simple parameter type
@ParameterType("\\d{4}-\\d{2}-\\d{2}")
public LocalDate date(String dateString) {
return LocalDate.parse(dateString);
}
// Custom name parameter type
@ParameterType(value = "\\d+", name = "number")
public BigInteger bigNumber(String numberString) {
return new BigInteger(numberString);
}
// Multi-capture group parameter type
@ParameterType("(\\d{1,2})/(\\d{1,2})/(\\d{4})")
public LocalDate usDate(String month, String day, String year) {
return LocalDate.of(Integer.parseInt(year),
Integer.parseInt(month),
Integer.parseInt(day));
}
// Enum parameter type
@ParameterType("red|green|blue")
public Color color(String colorName) {
return Color.valueOf(colorName.toUpperCase());
}
// Complex object parameter type
@ParameterType("([A-Za-z]+)\\s+([A-Za-z]+)\\s+<([^>]+)>")
public Person person(String firstName, String lastName, String email) {
return new Person(firstName, lastName, email);
}
// Parameter type for snippets
@ParameterType(value = "\\d+\\.\\d{2}", useForSnippets = true)
public Money money(String amount) {
return new Money(new BigDecimal(amount));
}
// Preferential parameter type
@ParameterType(value = "\\d+", preferForRegexMatch = true)
public CustomNumber customNumber(String number) {
return new CustomNumber(Integer.parseInt(number));
}
}
// Usage in step definitions
@Given("the date is {date}")
public void the_date_is(LocalDate date) {
// date is automatically converted from string
}
@When("I transfer {money} to {person}")
public void transfer_money(Money amount, Person recipient) {
// Both parameters automatically converted
}Register default parameter transformer for handling all unmatched parameter conversions.
/**
* Register default parameter transformer
* Method signatures supported:
* - String, Type -> Object (transform from string)
* - Object, Type -> Object (transform from any object)
*/
@DefaultParameterTransformer
public Object defaultTransformer(String fromValue, Type toValueType) {
// Default transformation logic
return transformedValue;
}
/**
* Alternative signature for object transformation
*/
@DefaultParameterTransformer
public Object defaultTransformer(Object fromValue, Type toValueType) {
// Transform from any object type
return transformedValue;
}Usage Examples:
import io.cucumber.java.DefaultParameterTransformer;
import com.fasterxml.jackson.databind.ObjectMapper;
import java.lang.reflect.Type;
public class DefaultTransformers {
private final ObjectMapper objectMapper = new ObjectMapper();
// JSON-based default transformer using Jackson
@DefaultParameterTransformer
public Object defaultTransformer(String fromValue, Type toValueType) {
try {
// Try JSON transformation first
return objectMapper.readValue(fromValue,
objectMapper.constructType(toValueType));
} catch (Exception e) {
// Fallback to string-based transformation
if (toValueType.equals(Integer.class)) {
return Integer.valueOf(fromValue);
}
if (toValueType.equals(Boolean.class)) {
return Boolean.valueOf(fromValue);
}
if (toValueType.equals(LocalDate.class)) {
return LocalDate.parse(fromValue);
}
return fromValue; // Return as string if no transformation
}
}
// Enum-aware default transformer
@DefaultParameterTransformer
public Object enumTransformer(String fromValue, Type toValueType) {
if (toValueType instanceof Class && ((Class<?>) toValueType).isEnum()) {
Class<? extends Enum> enumClass = (Class<? extends Enum>) toValueType;
return Enum.valueOf(enumClass, fromValue.toUpperCase());
}
return fromValue;
}
}
// Usage - these will use default transformer
@Given("the status is {Status}") // Uses enum transformer
public void the_status_is(Status status) { }
@When("the config is {Config}") // Uses JSON transformer
public void the_config_is(Config config) { }Register transformers for converting data table entries into custom objects.
/**
* Register data table type transformer
* @param replaceWithEmptyString Values to replace with empty string (default: {})
*/
@DataTableType(replaceWithEmptyString = {"[blank]", "[empty]"})
public CustomObject tableEntryTransformer(Map<String, String> entry) {
return new CustomObject(entry);
}
/**
* Data table type for list transformation
*/
@DataTableType
public CustomObject listTransformer(List<String> row) {
return new CustomObject(row.get(0), row.get(1));
}
/**
* Data table type for single cell transformation
*/
@DataTableType
public CustomObject cellTransformer(String cell) {
return new CustomObject(cell);
}Usage Examples:
import io.cucumber.java.DataTableType;
import java.util.Map;
import java.util.List;
public class DataTableTypes {
// Transform table rows to Person objects
@DataTableType
public Person personEntry(Map<String, String> entry) {
return new Person(
entry.get("name"),
entry.get("email"),
Integer.parseInt(entry.get("age"))
);
}
// Transform with empty string replacement
@DataTableType(replaceWithEmptyString = {"", "[empty]", "null"})
public Product productEntry(Map<String, String> entry) {
return new Product(
entry.get("name"),
entry.get("description").isEmpty() ? null : entry.get("description"),
new BigDecimal(entry.get("price"))
);
}
// Transform list rows to objects
@DataTableType
public Coordinate coordinateRow(List<String> row) {
return new Coordinate(
Double.parseDouble(row.get(0)), // x
Double.parseDouble(row.get(1)) // y
);
}
// Single cell transformation
@DataTableType
public Currency currencyCell(String cell) {
return Currency.getInstance(cell);
}
}
// Usage in step definitions
@Given("the following users exist:")
public void users_exist(List<Person> users) {
// Each table row automatically converted to Person
}
@When("I create products:")
public void create_products(List<Product> products) {
// Each table row automatically converted to Product
}Register default transformers for data table entries and cells.
/**
* Register default data table entry transformer
* @param headersToProperties Convert headers to properties format (default: true)
* @param replaceWithEmptyString Values to replace with empty string (default: {})
*/
@DefaultDataTableEntryTransformer(headersToProperties = true,
replaceWithEmptyString = {})
public Object defaultEntryTransformer(Map<String, String> entry, Type type) {
return transformedEntry;
}
/**
* Register default data table cell transformer
* @param replaceWithEmptyString Values to replace with empty string (default: {})
*/
@DefaultDataTableCellTransformer(replaceWithEmptyString = {})
public Object defaultCellTransformer(String cell, Type type) {
return transformedCell;
}Usage Examples:
import io.cucumber.java.DefaultDataTableEntryTransformer;
import io.cucumber.java.DefaultDataTableCellTransformer;
import com.fasterxml.jackson.databind.ObjectMapper;
import java.lang.reflect.Type;
public class DefaultDataTableTransformers {
private final ObjectMapper objectMapper = new ObjectMapper();
// JSON-based default entry transformer
@DefaultDataTableEntryTransformer
public Object defaultEntryTransformer(Map<String, String> entry, Type type) {
try {
// Convert map to JSON then to target type
String json = objectMapper.writeValueAsString(entry);
return objectMapper.readValue(json,
objectMapper.constructType(type));
} catch (Exception e) {
throw new RuntimeException("Failed to transform entry: " + entry, e);
}
}
// Property-style header conversion
@DefaultDataTableEntryTransformer(headersToProperties = true)
public Object propertyEntryTransformer(Map<String, String> entry, Type type) {
// Headers like "First Name" become "firstName" properties
Map<String, String> propertyMap = entry.entrySet().stream()
.collect(Collectors.toMap(
e -> toCamelCase(e.getKey()),
Map.Entry::getValue
));
return createObjectFromProperties(propertyMap, type);
}
// Default cell transformer with multiple replacements
@DefaultDataTableCellTransformer(replaceWithEmptyString = {
"[blank]", "[empty]", "null", "N/A", ""
})
public Object defaultCellTransformer(String cell, Type type) {
// Handle common type conversions
if (type.equals(Integer.class) && !cell.isEmpty()) {
return Integer.valueOf(cell);
}
if (type.equals(Boolean.class)) {
return Boolean.valueOf(cell);
}
if (type.equals(LocalDate.class) && !cell.isEmpty()) {
return LocalDate.parse(cell);
}
return cell;
}
private String toCamelCase(String header) {
return Arrays.stream(header.split("\\s+"))
.map(word -> word.toLowerCase())
.collect(Collectors.joining())
.replaceFirst("^.", header.substring(0, 1).toLowerCase());
}
}Register transformers for converting doc strings into custom objects.
/**
* Register doc string type transformer
* @param contentType Content type for matching (default: "" - uses method name)
*/
@DocStringType(contentType = "json")
public CustomObject docStringTransformer(String docString) {
return parseDocString(docString);
}
/**
* Doc string type using method name as content type
*/
@DocStringType
public XmlDocument xml(String docString) {
return XmlDocument.parse(docString);
}Usage Examples:
import io.cucumber.java.DocStringType;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
public class DocStringTypes {
private final ObjectMapper objectMapper = new ObjectMapper();
// JSON doc string transformer
@DocStringType
public JsonNode json(String docString) {
try {
return objectMapper.readTree(docString);
} catch (Exception e) {
throw new RuntimeException("Invalid JSON: " + docString, e);
}
}
// XML doc string transformer
@DocStringType
public Document xml(String docString) {
try {
DocumentBuilder builder = DocumentBuilderFactory
.newInstance()
.newDocumentBuilder();
return builder.parse(new InputSource(new StringReader(docString)));
} catch (Exception e) {
throw new RuntimeException("Invalid XML: " + docString, e);
}
}
// Custom content type
@DocStringType(contentType = "yaml")
public Map<String, Object> yamlContent(String docString) {
return yamlParser.parse(docString);
}
// Configuration object from properties
@DocStringType(contentType = "properties")
public Properties propertiesContent(String docString) {
Properties props = new Properties();
try {
props.load(new StringReader(docString));
return props;
} catch (Exception e) {
throw new RuntimeException("Invalid properties: " + docString, e);
}
}
}
// Usage in step definitions
@Given("the following JSON configuration:")
public void json_configuration(JsonNode config) {
// DocString automatically parsed as JSON
}
@When("I send the XML request:")
public void send_xml_request(Document xmlDoc) {
// DocString automatically parsed as XML
}
@Then("the YAML response should be:")
public void yaml_response(Map<String, Object> yaml) {
// DocString automatically parsed as YAML
}Use the @Transpose annotation to transpose data tables before transformation.
/**
* Transpose annotation for data table parameters
* @param value Whether to transpose the table (default: true)
*/
public void stepWithTransposedTable(@Transpose DataTable table) { }
/**
* Transpose with explicit value
*/
public void stepWithTransposedTable(@Transpose(true) List<Map<String, String>> data) { }
/**
* Conditional transposition
*/
public void stepWithConditionalTranspose(@Transpose(false) DataTable table) { }Usage Examples:
import io.cucumber.java.Transpose;
import io.cucumber.datatable.DataTable;
public class TransposeExamples {
// Transpose data table for vertical layout
@Given("the user has the following profile:")
public void user_profile(@Transpose DataTable profileData) {
// Original table: | name | John |
// | email | john@...|
// | age | 30 |
//
// After transpose: | name | email | age |
// | John | john@... | 30 |
}
// Transpose list of maps
@Given("the settings are configured:")
public void settings_configured(@Transpose List<Map<String, String>> settings) {
// Transposed before converting to list of maps
for (Map<String, String> setting : settings) {
String key = setting.get("key");
String value = setting.get("value");
}
}
// Conditional transpose
@Given("the matrix data:")
public void matrix_data(@Transpose(false) DataTable matrix) {
// Table is NOT transposed
}
// Transpose with custom objects
@Given("the product attributes:")
public void product_attributes(@Transpose List<Attribute> attributes) {
// Table transposed then converted to Attribute objects
}
}Install with Tessl CLI
npx tessl i tessl/maven-io-cucumber--cucumber-java