Getting Started
SpecBinder is a compile-time code generator that turns Gherkin .feature or .specb files into pure JUnit 5 test classes.
This guide walks you through your first SpecBinder feature, end to end.
Requirements
- Java 21+
- JUnit 5
- Maven (or Gradle) with annotation processing enabled
- An IDE with APT enabled (IntelliJ IDEA recommended — see the IntelliJ Plugin page)
1. Add the dependencies
Add the SpecBinder annotations and annotation-processor artifacts to your project. With Maven:
<dependencies>
<!-- Marker annotation: @Gherkin2JUnit, @Gherkin2JUnitOptions -->
<dependency>
<groupId>dev.specbinder</groupId>
<artifactId>annotations</artifactId>
<version>2026.39.0</version>
<scope>test</scope>
</dependency>
<!-- The annotation processor that generates test classes at compile time -->
<dependency>
<groupId>dev.specbinder</groupId>
<artifactId>annotation-processor</artifactId>
<version>2026.39.0</version>
<scope>provided</scope>
</dependency>
</dependencies>
You'll also want JUnit Jupiter:
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter</artifactId>
<scope>test</scope>
</dependency>
2. Write a feature file
Create src/test/resources/specs/ShoppingCart.feature:
Feature: ShoppingCart
A shopper can add items to the cart and check the cart subtotal.
Scenario: Add item and verify cart contents
Given I have an empty shopping cart
When I add "Wireless Headphones" with quantity "2" and unit price "59.99"
Then the cart should contain "1" item
And the cart subtotal should be "119.98"
Both .feature and .specb extensions are supported.
3. Create the marker class
The marker class is what tells SpecBinder which spec to process. Annotate it with @Gherkin2JUnit and point it at your spec file:
package com.example.shop;
import dev.specbinder.annotations.Gherkin2JUnit;
@Gherkin2JUnit("specs/ShoppingCart.feature")
public abstract class ShoppingCartFeature {
// No members required yet — step implementations come later.
}
A few things to know about the marker class:
- It is conventionally declared
abstract. The generator emits an abstract subclass (ShoppingCartScenarios) which you then extend with your concrete test implementation (more on this below). - The argument to
@Gherkin2JUnitis the path to the spec file, relative to your test resources / source roots. - The path is optional. A bare
@Gherkin2JUnitenables convention-based discovery — it processes every.feature/.specbfile in the same package as the marker class.
4. Compile to generate the abstract test class
Run a compile:
mvn compile test-compile
Under target/generated-test-sources/, SpecBinder writes ShoppingCartScenarios.java. By default the generated class is abstract, with one abstract method per step and one @Test method per scenario:
@Generated("dev.specbinder.processor.AnnotationProcessor")
@DisplayName("ShoppingCart")
public abstract class ShoppingCartScenarios extends ShoppingCartFeature {
public abstract void iHaveAnEmptyShoppingCart();
public abstract void iAdd$p1WithQuantity$p2AndUnitPrice$p3(String p1, Integer p2, Double p3);
public abstract void theCartShouldContain$p1Item(Integer p1);
public abstract void theCartSubtotalShouldBe$p1(Double p1);
@Test
@DisplayName("Scenario: Add item and verify cart contents")
public void scenario_1() {
/*
* Given I have an empty shopping cart
*/
iHaveAnEmptyShoppingCart();
/*
* When I add "Wireless Headphones" with quantity "2" and unit price "59.99"
*/
iAdd$p1WithQuantity$p2AndUnitPrice$p3("Wireless Headphones", 2, 59.99);
/*
* Then the cart should contain "1" item
*/
theCartShouldContain$p1Item(1);
/*
* And the cart subtotal should be "119.98"
*/
theCartSubtotalShouldBe$p1(119.98);
}
}
The block comment above each call is the original Gherkin step text — preserved verbatim so the generated test reads top-to-bottom like the spec.
A few things to note about the generated step methods:
- Method names are derived from step text. Words are camelCased; the step keyword (
Given/When/Then/And/But) is dropped. Identical step text under different keywords reuses the same method. - Quoted values become parameters. Each
"…"in a step becomes a method parameter (positionally namedp1,p2, …) and a$p1,$p2, … placeholder is added to the method name to mark its slot. The generator infers the type:"true"/"false"→Boolean, integer literals →Integer/Long, decimals →Double, single characters →Character, everything else →String. - The class name suffix is configurable (
Scenariosby default for abstract generation) via@Gherkin2JUnitOptions(classSuffixIfAbstract = "...").
5. Implement the step methods
Because the generated class is abstract, you create a concrete subclass that extends it and implements every step method. This is where your real test code lives — including any state shared between steps (just plain instance fields):
package com.example.shop;
import java.util.ArrayList;
import java.util.List;
import static org.junit.jupiter.api.Assertions.assertEquals;
public class ShoppingCartTest extends ShoppingCartScenarios {
private final List<CartItem> cart = new ArrayList<>();
@Override
public void iHaveAnEmptyShoppingCart() {
cart.clear();
}
@Override
public void iAdd$p1WithQuantity$p2AndUnitPrice$p3(String name, Integer quantity, Double unitPrice) {
cart.add(new CartItem(name, quantity, unitPrice));
}
@Override
public void theCartShouldContain$p1Item(Integer expectedCount) {
assertEquals(expectedCount, cart.size());
}
@Override
public void theCartSubtotalShouldBe$p1(Double expectedSubtotal) {
double subtotal = cart.stream()
.mapToDouble(item -> item.quantity() * item.unitPrice())
.sum();
assertEquals(expectedSubtotal, subtotal, 0.001);
}
record CartItem(String name, int quantity, double unitPrice) {}
}
If any abstract step method is left unimplemented, the project simply will not compile — that's how SpecBinder turns "undefined step" into a compile-time error.
Parameter names in your implementation can be anything you like — only the method name and parameter types need to match the generated signature.
Sharing step implementations across features
Sometimes the same step shows up in many features (e.g. I am a signed-in shopper "..."). You can pull those implementations up into the marker class itself:
@Gherkin2JUnit("specs/ShoppingCart.feature")
public abstract class ShoppingCartFeature {
public void iHaveAnEmptyShoppingCart() {
// shared implementation
}
}
The generator inspects the marker class hierarchy and, for any step method it already finds there, does not emit an abstract declaration. The concrete subclass inherits those implementations and only needs to override the steps that are still missing.
6. Run the tests
mvn test
ShoppingCartTest runs as a standard JUnit 5 test class. You can also run it from your IDE, set breakpoints inside step or scenario test methods, and use Find Usages.
Abstract vs concrete generation mode
The workflow above is abstract mode, the default. The generator emits an abstract class (<Name>Scenarios) with abstract step methods and missing implementations become compile errors.
Concrete mode is the alternative — opt in with @Gherkin2JUnitOptions(shouldBeAbstract = false):
@Gherkin2JUnit("specs/ShoppingCart.feature")
@Gherkin2JUnitOptions(shouldBeAbstract = false)
public abstract class ShoppingCartFeature {
// ...
}
In concrete mode the generated class is named <Name>Test (configurable via classSuffixIfConcrete) and is itself runnable — each step method has a body of Assertions.fail("Step is not yet implemented"). You implement steps by moving them up into the marker class; the generator detects them and stops emitting stubs. Tests can run from day one, but missing implementations only surface at run time, not compile time.
See the Configuration page for the full list of options.
A tip on file layout
Putting .feature / .specb files in src/test/resources/... works fine. One alternative worth considering is placing them right next to their marker classes in src/test/java/... — the IDE then shows the spec and the implementation side by side, and @Gherkin2JUnit with no path argument will discover them automatically.
To make Maven pick up specs from src/test/java/, add:
<build>
<testResources>
<testResource>
<directory>src/test/java</directory>
<includes>
<include>**/*.feature</include>
<include>**/*.specb</include>
</includes>
</testResource>
<testResource>
<directory>src/test/resources</directory>
</testResource>
</testResources>
</build>
Next steps
- Configuration — every
@Gherkin2JUnitOptionsflag explained. - IntelliJ Plugin — syntax highlighting, navigation, auto-recompile on spec edit, inline results from the execution report.
- Browse the examples directory in the SpecBinder repo:
Hello World,Step Parameters,Rules,Background,Scenario Outline,Tags,Data Tables,TDD Workflow, and more.