engineering // show & tell

BDD as Agent-Readable Documentation

How Cucumber scenarios make code legible to AI agents

cucumber java bdd

The Bug That Started It All

Users See {product_name} in Order Confirmation

Out-of-stock products, API timeouts, and catalog sync delays cause template resolution to fail. Without filtering, raw placeholders leak to the user.

OPS-1248 order-service
BEFORE

Raw Java: What the Agent Reads

// Old bestAvailableFallback — no filtering
private List<OrderConfirmation> bestAvailableConfirmation(
    String channel, String productType,
    String locale, int maxItems) {
  List<OrderConfirmation> confirmations =
      templateProvider.getConfirmations(
          channel, productType, locale, maxItems);
  if (confirmations.size() >= maxItems)
    return confirmations;  // {product_name} leaks here!
}

The agent has to trace through template loading, product resolution, and exception handlers to understand what can go wrong

AFTER

Gherkin: What the Agent Understands

Scenario: Out-of-stock product shows clean confirmation

  Given the product type is "ELECTRONICS"
  And the product ID is "SKU-12345"
  When the catalog service returns no information
  And order confirmations are requested
  Then all confirmations should not contain any placeholder pattern

The agent grasps the invariant instantly: when the catalog fails, no placeholders leak to the user

Seven Scenarios, Seven Behaviors

Product catalog unavailable
No product name resolved → confirmations stay clean, no {product_name}
Catalog service down
Service unavailable → no placeholder pattern in any confirmation
Out-of-stock product
Unavailable item → clean confirmations with no placeholder leaks
Catalog lookup succeeds (product)
"MacBook Pro" fills templates, no raw {product_name} remains
Catalog lookup succeeds (category)
"Wireless Headphones" fills templates, mix of templated and non-templated
All product types
ELECTRONICS, CLOTHING, BOOKS, FOOD, HOME → all clean when catalog fails
Generic NONE type
No product context → never contains placeholders

Scenario: Catalog Succeeds

Scenario: Catalog succeeds and templates are properly resolved

  Given the product type is "ELECTRONICS"
  And the product ID is "SKU-67890"
  When the catalog service returns "MacBook Pro" as the product name
  And order confirmations are requested
  Then some confirmations should contain "MacBook Pro"
  And no confirmations should contain "{product_name}"
  And all confirmations should have resolved text

Happy path and safety check in one scenario: templates resolve AND no placeholders leak

Feature File → Step Definitions

.feature
ConfirmationStepDefinitions.java
Given the template provider is enabled
@Given("the template provider is enabled") public void theTemplateProviderIsEnabled() { templateProvider = new TemplateProvider(true); resolver = new ProductTemplateResolver(catalogClient, templateProvider); }
And the product type is "ELECTRONICS"
@Given("the product type is {string}") public void theProductTypeIs(String productType) { this.productType = productType; ProductType.fromString(productType).ifPresent(t -> this.type = t); }
When the catalog service returns no information
@When("the catalog service returns no information") public void theCatalogServiceReturnsNoInformation() { when(catalogClient.getProductNames(any(Context.class), eq(type), any())) .thenReturn(completedFuture(Map.of())); }
And order confirmations are requested
@When("order confirmations are requested") public void orderConfirmationsAreRequested() { result = resolver.resolve(Context.current(), type, List.of(productId), channel, locale, maxItems) .toCompletableFuture().join(); }
Then all confirmations should not contain any placeholder pattern
@Then("all confirmations should not contain any placeholder pattern") public void allConfirmationsNoPlaceholders() { assertThat(result).allSatisfy(c -> { assertThat(PLACEHOLDER_PATTERN.matcher(c.text()).find()) .as("Should not contain placeholders: " + c.text()) .isFalse(); }); }

Scaling with Scenario Outlines

One Gherkin block, 10 test executions. The agent sees every combination at a glance.

Scenario: All locales have product confirmation supply

  Given the product type is "<productType>"
  And the locale is "<locale>"
  When the catalog service returns no information
  And order confirmations are requested
  Then some confirmations should contain a subject line
  And some confirmations should contain shipping details
  And all confirmations should not contain any placeholder pattern

  Examples:
    | productType  | locale |
    | ELECTRONICS  | en-us  |
    | CLOTHING     | en-us  |
    | ELECTRONICS  | de-de  |
    | CLOTHING     | de-de  |
    | ELECTRONICS  | ja-jp  |
    | CLOTHING     | ja-jp  |
    | ...4 more rows       |
10 test cases from one scenario block. Every product type, every locale. An agent reads the table and knows exactly what's covered.

Edge Cases the Agent Learns From

Per-product-type coverage
Scenario Outline: All types stay clean
  Given the product type is "<productType>"
  When catalog returns no information
  Then no placeholder pattern

  Examples:
    | productType |
    | ELECTRONICS |
    | CLOTHING    |
    | BOOKS       |
    | FOOD        |
Generic NONE type
Scenario: NONE confirmations
  Given the product type is "NONE"
  When order confirmations requested
  Then no placeholder pattern

Scenario Outline runs the same test across every product type. One Gherkin block, four test executions.

BDD for Design Alignment

PR review says "use three confidence layers." Write the scenarios first, agree on the wording, then implement.

Scenario: Express shipping when confidence is HIGH
  Given a shipping option with text "delivers to {address}"
  When the shipping provider recommends "123 Main St" with HIGH confidence
  Then a resolved option "delivers to 123 Main St" should be present

Scenario: Generic fallback when confidence is LOW
  Given a shipping option with text "delivers to {address}"
  And a shipping option with text "standard shipping available"
  When the shipping provider recommends "123 Main St" with LOW confidence
  Then a resolved option "standard shipping available" should be present
  And no resolved option should contain "123 Main St"

Scenario: No option when no recommendation exists
  When the shipping provider has no recommendation
  Then no resolved options should be returned
HIGH/MEDIUM → resolved templates
LOW → static fallbacks
NONE → empty response

How It Grew: 7 → 50+ Scenarios

The original placeholder fix seeded a living spec across 8 feature files

May: The Start
7 scenarios for placeholder filtering. Introduced Cucumber, RunCucumberTest runner, and the step definitions framework.
June-July: Product Coverage
Product type coverage, locale parity, category resolution
Aug-Sep: Features + Safety
Shipping provider, express checkout, payment confirmation, address validation

The Three-Layer Link

📄
.feature file (Gherkin)
Declares behavior in domain language
↕
🔗
Step Definitions (Java)
Translates Gherkin to Java, the bridge
↕
⚙️
Production Code
The actual implementation under test

Why This Matters for Agentic Development

DOCUMENTATION

Plain text Cucumber scenarios are the most agent-readable format. No AST parsing, no class hierarchy traversal.

SPECIFICATION

Every use case is an executable spec. Agents know exactly what the system does and what edge cases are handled.

COMMUNICATION

Gherkin is the common language between humans, agents, and CI. Everyone reads the same source of truth.

Context Files: Teaching Agents How to Work Here

The service ships both CLAUDE.md and AGENTS.md with the same BDD instructions. Any agent that opens the repo learns the rules.

How to Work
1. STOP before writing code 2. Draft the .feature file first 3. Show it for approval 4. Implement steps + code together 5. Run RunCucumberTest 6. Fix failures without asking
How to Write Gherkin
DO: Domain language, declarative
When the catalog service returns no information
DON'T: Class names, method names
When catalogClient throws NotFoundException

A new agent reads CLAUDE.md to learn the workflow, then reads .feature files to learn every behavior. Day one contributor.

The Agent Workflow: /bdd-first

Scenarios are cheap. Implementation plans are not. Agree on behavior first.

Phase 1: Gherkin Spec
1. Read the ticket 2. Draft .feature file 3. Present for review 4. STOP and wait
15 lines of plain text. Seconds to fix if wrong.
Phase 2: Plan + Implement
1. Plan references scenarios 2. Implement steps + code 3. Run RunCucumberTest 4. Scenarios pass = done
Only runs after scenarios are approved.
Best for
Deterministic behavior: fallback selection, template resolution, confidence routing, entity coverage, input validation
Skip for
ML ranking, exploratory UX, pure refactoring, performance work. The "right answer" needs to be knowable upfront.

What does an AI agent see
when it reads your service?

The same code that's hard for humans to parse at a glance is even harder for agents to reason about

Takeaways

  • Cucumber scenarios are the most agent-readable documentation you can write
  • Every scenario is an executable spec that CI validates on every commit
  • The three-layer link keeps Gherkin clean while step definitions do the bridging

What's Next

  • Broader adoption across services

// order-service // 2026█

Onboard Your Service to Cucumber

Everything you need to add BDD to a backend service.

Wiring Checklist
1. Add cucumber-java + cucumber-junit-platform-engine to your build config 2. Create RunCucumberTest.java (JUnit Platform launcher) 3. Add cucumber.properties in test resources 4. Write first .feature file 5. Write StepDefinitions.java 6. Run your test suite
Getting Started
1. Add Cucumber dependencies to your build tool (Maven, Gradle, Bazel)
2. Create RunCucumberTest runner with JUnit Platform
3. Write your first .feature file for the most error-prone path
4. Implement step definitions that bridge Gherkin to Java
5. Add CLAUDE.md instructions so agents follow the BDD workflow
6. Expand coverage one feature file at a time

Start with one feature file for your most error-prone path. The framework pays for itself on the second bug fix.

Saved