skip to content

How do you register a custom Cucumber Expression parameter type, and where must that registration live?

level: seniorimportance: should knowfreq 44%

answer

  1. give the domain a placeholder of its own
  2. pattern plus transformer plus a name
  3. it is code, so it must be loaded
  4. the error names the parameter type, not the step

basics

~20 s

Register it in code the runner already loads with your step definitions: in Cucumber-JVM a method annotated @ParameterType, in cucumber-js a defineParameterType call in a support file. The name then becomes a placeholder that converts matched text into a domain object.

solid answer

~40 s

A custom parameter type gives a **Cucumber Expression** its own placeholder, with a pattern and a transformer that turns matched text into a domain object — so a step method takes `Turbine` instead of `String`. In **Cucumber-JVM** you annotate a method with `@ParameterType`; the annotation value is the pattern, the method name is the placeholder name unless you set one, and the return type is what the step receives. In **cucumber-js** you call `defineParameterType` with a name, a regexp and a transformer. **Behave** uses a `register_type` call with its parse matcher; **SpecFlow/Reqnroll** use a `[StepArgumentTransformation]` method. The registration must be loaded as part of the same support code as the step definitions — otherwise the expression cannot be compiled and the run fails with an undefined parameter type error, not a missing-step message.

code

java · 9 lines
java
@ParameterType("T-[0-9]{3}")
public Turbine turbine(String id) {
    return turbineRegistry.byId(id);
}

@Given("turbine {turbine} is offline for maintenance")
public void turbineIsOffline(Turbine turbine) {
    plan.markOffline(turbine);
}

go deeper

for a junior

Know that the placeholders in a step-definition pattern are not limited to the built-in set, and that a project can define its own so steps take domain objects instead of strings.

for a middle

Explain the three pieces of a registration — name, matching pattern, transformer — and name the mechanism in the implementation you use, whether that is an annotated method or a define call.

for a senior

Show that you have debugged one: the registration must be loaded with the glue, and an unregistered placeholder fails the run naming the parameter type rather than reporting a missing step definition.

for a principal

Own the vocabulary. Decide which domain concepts earn a placeholder shared across teams, keep transformers pure, and stop the matching layer from becoming a second home for setup logic.

## What a custom parameter type buys The built-in placeholders of a **Cucumber Expression** hand your step-definition method primitives: numbers and strings. A **custom parameter type** lets you name your own placeholder, give it a pattern, and give it a transformer that turns the matched text into a domain object — so the method signature reads `turbineOffline(Turbine turbine)` rather than `turbineOffline(String id)` with a lookup in the first line of every step body. Three things come with it: - **One place for conversion.** Text-to-object logic lives in the transformer, not copied across every step definition that mentions the thing. - **A tighter match.** The type carries its own pattern, so `{turbine}` only matches text shaped like a turbine identifier; a sentence with a typo in the identifier does not silently bind. - **One place to reject bad input.** An unknown identifier throws in the transformer, and the failure points at the value rather than at a null halfway through the scenario. ## How each implementation registers one | Implementation | How you register it | Referenced in the pattern as | |---|---|---| | Cucumber-JVM | a method annotated `@ParameterType` whose pattern is the annotation value | `{methodName}`, or the name set on the annotation | | cucumber-js | a `defineParameterType` call taking a name, a regexp and a transformer | `{name}` | | Behave | a `register_type` call, with the default parse matcher | `{arg:Name}` in the step decorator | | SpecFlow/Reqnroll | a method marked `[StepArgumentTransformation]` in a binding class | selected by the method's return type | In Cucumber-JVM the annotated method's **name is the type name** unless the annotation sets one explicitly, its **return type** is what the step method will receive, and the annotation's string is the pattern the placeholder matches. A transformer method takes the matched text; where the pattern contains capture groups, each group is passed to it in order. ## Where the registration has to live This is the part candidates get wrong. The registration is not configuration and it is not magic: it is ordinary code, and Cucumber only knows about it if it is **loaded as part of the same support code as your step definitions**. In Cucumber-JVM that means the `@ParameterType` method sits in a class the runner already scans for glue; a helper class parked outside that code is invisible no matter how correct it is. In cucumber-js the `defineParameterType` call has to be in a support file the run loads, and it must have run before the expressions that mention the type are compiled. In Behave and SpecFlow/Reqnroll the same rule applies to the module or binding class holding the registration. The failure mode is distinctive and worth recognising on sight: an expression referencing a placeholder nobody registered cannot be compiled at all, so the run fails with an error naming the **undefined parameter type** rather than quietly reporting the step as undefined. Reading that message as "my step definition is missing" sends people hunting in the wrong place; the definition exists, its pattern cannot be built. ## Judgement: when it is worth it On a 63-scenario wind-farm maintenance planner suite that two teams both edit, custom parameter types pay for themselves in a narrow band: 1. **A concept that appears in many steps.** A turbine identifier appearing in 20-odd definitions is worth a type; a value used twice is not. 2. **A concept with a shape worth enforcing.** If the identifier has a real format, the type's pattern documents it and stops near-miss text binding to the step. 3. **A concept both teams already share.** A type invented by one team and unknown to the other is a private convenience that makes the other team's steps harder to read. 4. **A conversion that is genuinely total.** A transformer that can only sometimes produce a value turns every near-miss into a matching-time error rather than a readable assertion inside the step. Against that, weigh the cost: a placeholder that is not built into Cucumber is a word a newcomer must look up, and a transformer that reaches into shared mutable state is scenario-scoped setup smuggled into the matching layer. Keep transformers pure — text in, value out — and let hooks and injected state do setup. The interview signal here is the registration question rather than the syntax. Anyone can copy an annotated method from documentation; the person who has debugged a run that failed before a single scenario executed knows that the type is code the runner must load, and knows what the error message looks like when it has not.

  • A Cucumber-JVM expression uses {turbine} but the run fails before any scenario executes. What is the likely cause?
    The parameter type is not registered in code the runner loads with the glue, so the expression cannot be compiled and the failure names the undefined parameter type. Usually the annotated method sits in a helper class outside the scanned support code, or the name differs from the method name because the annotation sets one explicitly. It is a wiring problem, not a missing step definition.
  • Would you put a database lookup inside a parameter type's transformer?
    Only for a genuinely read-only resolution, and reluctantly. The transformer runs during step matching, so anything it touches becomes a hidden dependency of binding a sentence to code, and a failure there is reported as a conversion problem rather than as scenario setup. Keep transformers pure — text in, value out — and put anything stateful in hooks or injected scenario-scoped objects.
  • When is a custom parameter type not worth introducing?
    When the concept appears in a couple of steps, when it has no meaningful text shape to enforce, or when only one team recognises the word. Each custom placeholder is vocabulary a newcomer has to look up, and one that hides a plain string behind a domain name buys nothing. The bar is a concept used across many definitions whose format is worth documenting in the pattern.

saying these in an interview costs you the question

  • Thinks a parameter type is configuration rather than loaded code
  • Registers the transformer in a class the runner never loads
  • Expects an unregistered placeholder to make the step merely undefined
  • Puts scenario setup or mutable state inside a transformer
  • Invents a custom type for a value used in one or two steps