Configuration
SpecBinder's code-generation behaviour is controlled through the @Gherkin2JUnitOptions annotation.
Applying configuration
@Gherkin2JUnitOptions can be placed on:
- The marker class itself — applies to that feature only.
- A shared base class — applies to every marker class that extends it. The annotation is
@Inherited, so subclasses pick the options up automatically.
@Gherkin2JUnitOptions(addSourceLineNumbers = true)
public abstract class BaseFeatureOptions {
}
@Gherkin2JUnit("specs/ShoppingCart.feature")
public abstract class ShoppingCartFeature extends BaseFeatureOptions {
}
Selective override
A child class can place its own @Gherkin2JUnitOptions to override only the options it explicitly sets. Every other option keeps the inherited value:
@Gherkin2JUnitOptions(
addSourceLineNumbers = true,
useStepKeywordInStepMethodName = true
)
public abstract class BaseFeatureOptions {
}
@Gherkin2JUnit("specs/ShoppingCart.feature")
@Gherkin2JUnitOptions(useStepKeywordInStepMethodName = false) // only this one changes
public abstract class ShoppingCartFeature extends BaseFeatureOptions {
}
In the example above, ShoppingCartFeature still inherits addSourceLineNumbers = true — only the keyword-in-method-name option is locally overridden.
Generation mode
shouldBeAbstract — boolean, default true
Controls whether the generated test class is abstract (the default) or concrete.
true(default) — generatespublic abstract class <SpecFileName>Scenarios extends <MarkerClass>. Every step method is declared asabstract. You create a concrete subclass that overrides each step. Missing implementations are compile errors.false— generatespublic class <SpecFileName>Test extends <MarkerClass>. Step methods have bodies (seeunimplementedStepBehaviorbelow). The class is immediately runnable; you implement steps by moving them into the marker class, where the generator will detect them and stop emitting stubs.
<SpecFileName> is the spec file's name without its extension — not the marker class name and not the Feature: title. See where the generated class name and package come from.
classSuffixIfAbstract — String, default "Scenarios"
The suffix appended to the spec file name when shouldBeAbstract = true. For a spec file specs/Checkout.specb, the default produces CheckoutScenarios.
classSuffixIfConcrete — String, default "Test"
The suffix appended to the spec file name when shouldBeAbstract = false. For a spec file specs/Checkout.specb, the default produces CheckoutTest.
unimplementedStepBehavior — enum, default FAIL
Only relevant when shouldBeAbstract = false. Controls the body of unimplemented step stubs in the generated concrete class:
| Value | Body |
|---|---|
FAIL (default) | Assertions.fail("Step is not yet implemented") — test fails at run time when the step is hit. |
SKIP | Assumptions.assumeTrue(false, "Step is not yet implemented") — test is reported as skipped/aborted. |
COMPILATION_ERROR | A non-Java token is emitted so the project will not compile until the step is implemented. Useful if you want concrete mode's runnable class plus abstract mode's compile-time safety. |
Empty Gherkin elements
emptyScenarioBehavior — enum, default FAIL
Behaviour for Scenario blocks that have no steps yet.
| Value | Behaviour |
|---|---|
FAIL (default) | Emits Assertions.fail("Scenario has no steps"). |
SKIP | Emits Assumptions.assumeTrue(false, ...) — reported as skipped. |
COMPILATION_ERROR | The project will not compile until the scenario has steps. |
emptyRuleBehavior — enum, default FAIL
Same three values; applies to Rule blocks that contain no scenarios.
tagForEmptyScenarios — String, default "new"
JUnit @Tag value automatically added to empty scenarios. Set to "" to disable.
tagForEmptyRules — String, default "new"
JUnit @Tag value automatically added to empty rules. Set to "" to disable.
Data tables
dataTableParameterType — enum, default LIST_OF_OBJECT_PARAMS
Controls how Gherkin data tables are represented as parameters on the generated step methods.
| Value | Generated parameter type | Notes |
|---|---|---|
LIST_OF_OBJECT_PARAMS (default) | List<XxxParam> where XxxParam is a generated record-like inner class | Strongest type safety. Column headers become typed fields with inferred types (String, Integer, Long, Double, Boolean, Character). |
LIST_OF_MAPS | List<Map<String, String>> | All values are strings; convert at the call site. |
CUCUMBER_DATA_TABLE | Cucumber's io.cucumber.datatable.DataTable | Requires getTableConverter() in the class hierarchy. Useful if you're already invested in Cucumber's data-table API. |
With LIST_OF_OBJECT_PARAMS, the generator emits a typed wrapper:
Given my cart contains the following items:
| name | qty | price | category |
| Wireless Headphones | 1 | 60.00 | electronics |
public void myCartContainsTheFollowingItems(List<ItemsParam> items) {
// your implementation
}
public static class ItemsParam {
public String name() { /* ... */ return null; }
public Integer qty() { /* ... */ return null; }
public Double price() { /* ... */ return null; }
public String category() { /* ... */ return null; }
}
You can move the ItemsParam class up into the marker class and refine its field types — for example, changing String category to an enum Category. The generator then uses your refined class and the project fails to compile if a spec row contains an unknown category. This turns spec/data drift into a compile error.
useQualifiedEnumConstants — boolean, default false
When generated code references an enum constant in a LIST_OF_OBJECT_PARAMS table, controls whether the constant is referenced as AVAILABLE (with a static import — default) or Status.AVAILABLE (qualified by enum type name). The qualified form is more verbose but reads better when several enums are in play.
Step method naming
useStepKeywordInStepMethodName — boolean, default false
Controls whether the Gherkin keyword (Given/When/Then) is included as a method-name prefix.
| Step | false (default) | true |
|---|---|---|
Given user exists | userExists() | givenUserExists() |
When user exists | userExists() | whenUserExists() |
Then user exists | userExists() | thenUserExists() |
The default collapses identical step text under different keywords into a single shared method — typically what you want.
Cucumber interop
addCucumberStepAnnotations — boolean, default false
When true, the generator adds @Given / @When / @Then annotations from io.cucumber.java.en.* on generated step methods. Useful if an IDE Cucumber/Gherkin plugin is in play and you want navigation from spec text → step method to work through those annotations.
useCucumberAnnotationsForStepMatching — boolean, default false
When true, the processor inspects Cucumber step annotations on methods in the class hierarchy to determine whether a step is already implemented — independent of method name. A method annotated with @Given("user exists") is recognised as the implementation of Given user exists even if the method itself is named setupUser.
Both regular expressions and Cucumber expressions are supported in the annotation value. Cucumber expressions support these built-in parameter types: {int}, {long}, {short}, {byte}, {float}, {double}, {bigdecimal}, {biginteger}, {word}, {string}, and the anonymous {}. Custom parameter types are not supported.
If both an annotation-matched method and a name-matched method exist for the same step, the annotation match wins.
Spec file discovery
supportedFileExtensions — String[], default {"feature", "specb"}
Extensions (without the leading dot) recognised by convention-based discovery (@Gherkin2JUnit with no value) and by glob patterns. When @Gherkin2JUnit("explicit/path.txt") provides an explicit path, the file is processed regardless of its extension.
skipGenerationForTags — String[], default {}
Regex patterns matched against tags on Feature / Rule / Scenario / Examples. When any tag on an element matches, the corresponding generated element (test class, @Nested rule, @Test method, or Examples row set) is omitted entirely — useful for excluding manual or work-in-progress specs without deleting them.
@Gherkin2JUnitOptions(skipGenerationForTags = {"manual", "wip-.*", "(?i)ignore"})
This would skip generation for elements tagged @manual, anything starting with @wip-, or @ignore (case-insensitive).
Generated code diagnostics
addSourceLineNumbers — boolean, default false
When true, the generator embeds spec file line numbers in two places:
@DisplayNameon scenarios, rules, and backgrounds — e.g.@DisplayName("Scenario [12]: Successful login").- The block comment above each step call — e.g.
/* [13] Given user exists */.
Helpful when stepping through a generated test class and you want to jump back to the corresponding spec line.
emitScenarioHash — boolean, default true
Each generated @Test / @ParameterizedTest method is annotated with @ScenarioHash("<sha-256-hex>") — a hash of that scenario's executable content (background steps + scenario steps, including DocStrings and DataTables). It lets tooling detect when a scenario's meaning has changed across builds.
Set to false to emit no hash annotations. This affects more than the annotation itself: the execution report then also loses the original Gherkin step text, a Scenario Outline's template steps, typed step arguments, and any way to tell an outcome recorded against an older version of a scenario from one describing the spec as it stands — so the report becomes materially thinner. Turn it off only if you need to pin the exact shape of the generated code or the report JSON.
descriptionAsAnnotation — boolean, default false
Controls how Gherkin description text (the free-text lines under a Feature:, Rule:, Scenario: or Background: heading) is rendered on the generated class or method.
false(default) — the description becomes a JavaDoc block above the generated element.true— the description is emitted as a runtime-retained@Description("""…""")annotation instead, placed just below@DisplayName, so tooling (IDE plugins, execution reporters, custom JUnit listeners) can read it at runtime by reflection.
The two modes are mutually exclusive: an element gets the JavaDoc block or the @Description annotation, never both.
maxStringLiteralBytes — int, default 65000
The largest a single Java string literal in the generated class may be, measured in UTF-8 bytes. The JVM's hard limit is 65535; the default leaves headroom below it.
When a DocString exceeds the cap, the generator emits it as several "…" chunks joined with + instead of one text block, so a very large DocString (a base64-encoded image, say) cannot break compilation. Below the cap nothing changes. Most projects should leave this alone.
skipUnchangedSpecs — boolean, default false
Skips regenerating a test class when none of its inputs have changed since it was last generated — useful when a project has many spec files and incremental builds are slow.
When true, the generated class is stamped with a @SourceTimestamp recording the newest last-modified time across its inputs: the spec file, the class carrying @Gherkin2JUnit, and every source class in that class's hierarchy (which is where options usually live, so editing options forces regeneration). On a later run the class is left untouched if that value hasn't advanced. A generated class that is missing or carries no recorded timestamp — after a clean build, for instance — is always regenerated, so this can never leave stale output behind.
Detection follows the newest input time, so it only notices changes that advance that maximum. Ordinary editing does, because it sets the file's timestamp to now. A change that moves a timestamp backwards — checking out an older revision, restoring from an archive — does not, and the class will not be regenerated. Run a clean build after such a change.
Build output
verbosity — enum, default NORMAL
Controls how much the annotation processor writes to the build log. Each level is cumulative — it emits everything from the levels below it.
| Level | What it emits |
|---|---|
SILENT | Errors only. No banner, no summary. |
NORMAL (default) | Errors, warnings, startup banner, end-of-round summary. |
VERBOSE | Adds per-class headers, resolved feature paths, and reasons for skipped work. |
DEBUG | Adds full stack traces, parsed Gherkin AST summaries, JavaPoet model summaries, and per-step decisions. |
The annotation-level setting overrides the global -Aspecbinder.verbosity=… annotation-processor argument.
Experimental
enableCompositeSteps — boolean, default false
Enables composite steps, where a regular step followed by one or more *-prefixed sub-steps generates a wrapper method that delegates to the sub-steps via a lambda. Inspired by JBehave's textual composites.
Given customer "Alice" has product "Laptop" in shopping cart
* login as customer $p1
* search for product $p2
* add product to cart
* verify cart contains $p2
The generator emits a wrapper that lets the implementer either provide a custom default or run the listed sub-steps as the composite's body.
Experimental — API and exact code shape may change.
stripPatterns — String[], default {}
Regex patterns matching text to strip from a spec file before it is turned into test code. Every match is removed, so the shape of the pattern decides what disappears.
Teams often annotate specs with revision markers tying wording back to an issue tracker:
Given the user has a <CHANGED BR-123>premium</CHANGED BR-123> account
When the <REMOVED BR-789>legacy discount </REMOVED BR-789>is applied
Left in place, such markers become part of generated step method names — so adding or editing one renames an abstract step method and the hand-written test class implementing it no longer compiles. They also corrupt record field names derived from data table headers, and emit unbalanced HTML into JavaDoc.
Match only a marker and the text it wrapped survives:
@Gherkin2JUnitOptions(
stripPatterns = {"(?i)(<\\s*(NEW|CHANGED)\\s+[^<>]*>|</\\s*(NEW|CHANGED)\\b[^<>]*>)"}
)
Text is stripped everywhere it can appear: step text, Feature / Rule / Scenario names, descriptions, DocStrings, DataTables and Examples tables, including header cells, where a marker would otherwise corrupt generated field names and test method parameter names.
Markers are not treated as a balanced structure — each match is removed on its own, so an unpaired marker is removed just the same, and a marker pair may span any number of lines without affecting what sits between them.
Any line left holding only whitespace once a match is removed is dropped entirely, so removing a table row does not leave a gap that would terminate the table — note this shifts the source line numbers of everything below it.
<placeholder>Requiring whitespace and content after the keyword (<\s*CHANGED\s+[^<>]*>) is what stops a pattern from also matching a Scenario Outline placeholder such as <changed>. A looser pattern strips the placeholder, silently removing the step's parameter and changing the generated method signature.
A match that takes all of a step's text but leaves its keyword behind fails the build with a message naming the line — include the step keyword inside the matched text instead.
Experimental — API and exact behaviour may change.
stripBetweenPatterns — @StripBetween[], default {}
Pairs of regex patterns marking the two ends of a span to strip. Everything from the start marker to the end marker is removed, markers included.
@Gherkin2JUnitOptions(
stripBetweenPatterns = {
@StripBetween(start = "<\\s*REMOVED\\b[^<>]*>", end = "</\\s*REMOVED\\b[^<>]*>")
}
)
This is the safer way to express what stripPatterns can also express as a single regex. Writing "(?is)<REMOVED[^<>]*>.*?</REMOVED[^<>]*>" by hand has three failure modes, and all three produce a successful build with quietly wrong output:
| Mistake | What happens |
|---|---|
omitting (?s) | a span crossing a newline silently does not match |
.* instead of .*? | the span runs to the last closing marker in the file, deleting everything between |
| an unclosed marker | no match, no error, no hint |
Declaring the two ends separately removes the first two outright: the span is located by offset rather than by one regex, so no flag is needed to cross lines, and each start always pairs with the nearest following end.
A span may wrap whole Gherkin constructs — several steps, an entire Scenario, or rows of a DataTable or Examples table. Markers on their own lines give the cleanest result; a span that starts or ends mid-line leaves the remainder of that line at column zero.
- Nesting is not supported — a second
startappearing before the firstendis simply consumed by the outer span. - An unclosed span leaves the text untouched — a
startwith no followingendremoves nothing. - These pairs are applied before
stripPatterns, so a span still disappears wholesale even when a pattern there would also have matched its markers individually.
Experimental — API and exact behaviour may change.
Quick reference
| Option | Default |
|---|---|
shouldBeAbstract | true |
classSuffixIfAbstract | "Scenarios" |
classSuffixIfConcrete | "Test" |
unimplementedStepBehavior | FAIL |
emptyScenarioBehavior | FAIL |
emptyRuleBehavior | FAIL |
tagForEmptyScenarios | "new" |
tagForEmptyRules | "new" |
dataTableParameterType | LIST_OF_OBJECT_PARAMS |
useQualifiedEnumConstants | false |
useStepKeywordInStepMethodName | false |
addCucumberStepAnnotations | false |
useCucumberAnnotationsForStepMatching | false |
supportedFileExtensions | {"feature", "specb"} |
skipGenerationForTags | {} |
addSourceLineNumbers | false |
emitScenarioHash | true |
descriptionAsAnnotation | false |
maxStringLiteralBytes | 65000 |
skipUnchangedSpecs | false |
verbosity | NORMAL |
enableCompositeSteps | false (experimental) |
stripPatterns | {} (experimental) |
stripBetweenPatterns | {} (experimental) |