Skip to main content

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

shouldBeAbstractboolean, default true

Controls whether the generated test class is abstract (the default) or concrete.

  • true (default) — generates public abstract class <SpecFileName>Scenarios extends <MarkerClass>. Every step method is declared as abstract. You create a concrete subclass that overrides each step. Missing implementations are compile errors.
  • false — generates public class <SpecFileName>Test extends <MarkerClass>. Step methods have bodies (see unimplementedStepBehavior below). 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.
note

<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.

classSuffixIfAbstractString, default "Scenarios"

The suffix appended to the spec file name when shouldBeAbstract = true. For a spec file specs/Checkout.specb, the default produces CheckoutScenarios.

classSuffixIfConcreteString, 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:

ValueBody
FAIL (default)Assertions.fail("Step is not yet implemented") — test fails at run time when the step is hit.
SKIPAssumptions.assumeTrue(false, "Step is not yet implemented") — test is reported as skipped/aborted.
COMPILATION_ERRORA 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.

ValueBehaviour
FAIL (default)Emits Assertions.fail("Scenario has no steps").
SKIPEmits Assumptions.assumeTrue(false, ...) — reported as skipped.
COMPILATION_ERRORThe project will not compile until the scenario has steps.

emptyRuleBehavior — enum, default FAIL

Same three values; applies to Rule blocks that contain no scenarios.

tagForEmptyScenariosString, default "new"

JUnit @Tag value automatically added to empty scenarios. Set to "" to disable.

tagForEmptyRulesString, 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.

ValueGenerated parameter typeNotes
LIST_OF_OBJECT_PARAMS (default)List<XxxParam> where XxxParam is a generated record-like inner classStrongest type safety. Column headers become typed fields with inferred types (String, Integer, Long, Double, Boolean, Character).
LIST_OF_MAPSList<Map<String, String>>All values are strings; convert at the call site.
CUCUMBER_DATA_TABLECucumber's io.cucumber.datatable.DataTableRequires 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.

useQualifiedEnumConstantsboolean, 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

useStepKeywordInStepMethodNameboolean, default false

Controls whether the Gherkin keyword (Given/When/Then) is included as a method-name prefix.

Stepfalse (default)true
Given user existsuserExists()givenUserExists()
When user existsuserExists()whenUserExists()
Then user existsuserExists()thenUserExists()

The default collapses identical step text under different keywords into a single shared method — typically what you want.


Cucumber interop

addCucumberStepAnnotationsboolean, 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.

useCucumberAnnotationsForStepMatchingboolean, 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

supportedFileExtensionsString[], 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.

skipGenerationForTagsString[], 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

addSourceLineNumbersboolean, default false

When true, the generator embeds spec file line numbers in two places:

  • @DisplayName on 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.

emitScenarioHashboolean, 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.


descriptionAsAnnotationboolean, 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.

maxStringLiteralBytesint, 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.

skipUnchangedSpecsboolean, 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.

Limitation

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.

LevelWhat it emits
SILENTErrors only. No banner, no summary.
NORMAL (default)Errors, warnings, startup banner, end-of-round summary.
VERBOSEAdds per-class headers, resolved feature paths, and reasons for skipped work.
DEBUGAdds 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

enableCompositeStepsboolean, 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.

stripPatternsString[], 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.

Keep the pattern specific enough to miss <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:

MistakeWhat 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 markerno 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.

Behaviour at the edges
  • Nesting is not supported — a second start appearing before the first end is simply consumed by the outer span.
  • An unclosed span leaves the text untouched — a start with no following end removes 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

OptionDefault
shouldBeAbstracttrue
classSuffixIfAbstract"Scenarios"
classSuffixIfConcrete"Test"
unimplementedStepBehaviorFAIL
emptyScenarioBehaviorFAIL
emptyRuleBehaviorFAIL
tagForEmptyScenarios"new"
tagForEmptyRules"new"
dataTableParameterTypeLIST_OF_OBJECT_PARAMS
useQualifiedEnumConstantsfalse
useStepKeywordInStepMethodNamefalse
addCucumberStepAnnotationsfalse
useCucumberAnnotationsForStepMatchingfalse
supportedFileExtensions{"feature", "specb"}
skipGenerationForTags{}
addSourceLineNumbersfalse
emitScenarioHashtrue
descriptionAsAnnotationfalse
maxStringLiteralBytes65000
skipUnchangedSpecsfalse
verbosityNORMAL
enableCompositeStepsfalse (experimental)
stripPatterns{} (experimental)
stripBetweenPatterns{} (experimental)