Home Java Spring Cloud Contract: Consumer-Driven Contract Testing Guide
Advanced 4 min · July 14, 2026

Spring Cloud Contract: Consumer-Driven Contract Testing Guide

Learn consumer-driven contract testing with Spring Cloud Contract.

N
Naren Founder & Principal Engineer

20+ years shipping production Java in banking & fintech. Notes here come from systems that actually shipped.

Follow
Production
production tested
July 15, 2026
last updated
2,398
articles · all by Naren
Before you start⏱ 15-20 min read
  • Java 17+
  • Spring Boot 3.x
  • Maven or Gradle
  • Basic understanding of microservices and REST APIs
 ● Production Incident 🔎 Debug Guide ⚙ Triage Commands
Quick Answer
  • Use Spring Cloud Contract to define contracts between services as Groovy or Java DSL.
  • Contracts are verified by the producer; stubs are generated for consumers.
  • Key commands: mvn clean install runs verifier; mvn spring-cloud-contract:convert generates tests.
  • Always version your contracts and stubs.
  • Avoid over-specifying; keep contracts minimal for flexibility.
✦ Definition~90s read
What is Introduction to Spring Cloud Contract?

Spring Cloud Contract is a framework for consumer-driven contract testing that verifies API compatibility between microservices by defining expectations in contracts and generating tests and stubs.

Think of contract testing like a handshake agreement between two teams.
Plain-English First

Think of contract testing like a handshake agreement between two teams. Team A (consumer) says "I need you to give me X when I send Y." Team B (producer) agrees and writes a test to ensure they always deliver X. If B changes their behavior, the contract test breaks immediately, saving A from a production surprise.

In a microservices ecosystem, integration testing is the bane of every developer's existence. You've been there: a service changes its API ever so slightly, and suddenly your downstream consumer starts throwing 500s in production. The classic approach? Deploy both services, run end-to-end tests, and pray. That's slow, brittle, and doesn't scale.

Enter consumer-driven contract testing (CDCT). The idea is simple: the consumer defines what it expects from the producer, and the producer verifies it can meet those expectations. Spring Cloud Contract is the de facto tool for this in the Java ecosystem. It lets you define contracts as Groovy or Java DSL, auto-generates tests for the producer, and produces stubs that consumers can use in their own tests.

I've seen teams cut their integration testing time by 80% after adopting Spring Cloud Contract. But I've also seen teams misuse it — creating massive, brittle contracts that defeat the purpose. This guide will show you how to do it right, with real production lessons from the field.

What is Consumer-Driven Contract Testing?

Consumer-driven contract testing (CDCT) flips the traditional testing pyramid. Instead of writing integration tests that require all services to be up, each consumer defines its expectations in a contract. The producer then verifies it can fulfill those contracts. If a change breaks a contract, the producer's build fails immediately.

Spring Cloud Contract implements CDCT by allowing you to define contracts in Groovy or Java DSL. These contracts describe HTTP requests and responses (or messaging). From these contracts, the framework generates: - Producer-side tests: JUnit tests that verify the producer's API matches the contract. - Stubs: WireMock-compatible stubs that consumers can use in their tests.

Here's the hard truth: most teams get this wrong. They either write contracts that are too detailed (coupling consumer and producer implementations) or too vague (missing critical assertions). The key is to specify only what the consumer actually uses. If the consumer only needs transactionId from a response, don't assert on other fields like timestamp or internalStatus.

contract.groovyJAVA
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
Contract.make {
    description "Get payment by ID"
    request {
        method GET()
        url "/payments/123"
        headers {
            accept(applicationJson())
        }
    }
    response {
        status OK()
        headers {
            contentType(applicationJson())
        }
        body([
            transactionId: "txn-456",
            amount: 100.00,
            currency: "USD"
        ])
    }
}
💡Keep Contracts Minimal
📊 Production Insight
I once saw a team include every field from the database in a contract. When they added a new field, every consumer's contract broke, even though no consumer needed that field. That's the opposite of what you want.
🎯 Key Takeaway
Define contracts based on consumer needs, not producer capabilities.

Setting Up Spring Cloud Contract

Let's get practical. You'll need two modules: a producer and a consumer. For the producer, add the Spring Cloud Contract Verifier plugin and the spring-cloud-starter-contract-verifier dependency. For the consumer, add spring-cloud-starter-contract-stub-runner.

Producer setup (pom.xml): ``xml <plugin> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-contract-maven-plugin</artifactId> <version>4.1.0</version> <extensions>true</extensions> <configuration> <baseClassForTests>com.example.BaseTestClass</baseClassForTests> </configuration> </plugin> ``

Place your contracts in src/test/resources/contracts/. By default, the plugin generates tests in target/generated-test-sources/contracts/. You need a base test class that sets up the Spring context (e.g., with @SpringBootTest and @AutoConfigureMockMvc).

Consumer setup: Add the stub runner dependency and configure it to download stubs from a repository. In your test, use @AutoConfigureStubRunner with the stub coordinates.

``java @SpringBootTest @AutoConfigureStubRunner( stubsMode = StubRunnerProperties.StubsMode.LOCAL, ids = "com.example:payment-service:+:stubs:8090" ) class PaymentConsumerTest { // your test using WireMock stubs } ``

This will start a WireMock server on port 8090 that serves the stubs.

BaseTestClass.javaJAVA
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
package com.example;

import io.restassured.module.mockmvc.RestAssuredMockMvc;
import org.junit.jupiter.api.BeforeEach;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.web.context.WebApplicationContext;

@SpringBootTest
public abstract class BaseTestClass {

    @Autowired
    private WebApplicationContext context;

    @BeforeEach
    void setUp() {
        RestAssuredMockMvc.webAppContextSetup(context);
    }
}
🔥Stub Runner Modes
📊 Production Insight
Always set stubsMode = REMOTE in CI. I've seen builds pass because they used local stubs that were out of sync with the published ones.
🎯 Key Takeaway
Producer generates tests and stubs from contracts; consumer uses stubs via @AutoConfigureStubRunner.

Writing Contracts with Groovy DSL

Spring Cloud Contract offers two DSLs: Groovy and Java. I prefer Groovy for its conciseness, but Java works if you're allergic to Groovy. The Groovy DSL uses a Contract.make block with request and response sections.

Key elements: - description: Human-readable description of the contract. - request: Defines HTTP method, URL, headers, query params, and body. - response: Defines status, headers, and body. - bodyMatchers: For dynamic values like timestamps or IDs.

Example with body matchers: ``groovy Contract.make { description "Create payment" request { method POST() url "/payments" headers { contentType(applicationJson()) } body([ amount: 100.00, currency: "USD" ]) } response { status CREATED() headers { contentType(applicationJson()) } body([ id: "auto-generated", status: "PENDING", createdAt: "2024-01-01T00:00:00Z" ]) bodyMatchers { jsonPath('$.id', byRegex('[a-f0-9]{24}')) jsonPath('$.createdAt', byRegex('[0-9]{4}-[0-9]{2}-[0-9]{2}T[0-9]{2}:[0-9]{2}:[0-9]{2}Z')) } } } ``

Notice we use byRegex to match dynamic values without hardcoding them. This is critical for avoiding brittle tests.

payment_contract.groovyJAVA
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
Contract.make {
    description "Create payment"
    request {
        method POST()
        url "/payments"
        headers {
            contentType(applicationJson())
        }
        body([
            amount: 100.00,
            currency: "USD"
        ])
    }
    response {
        status CREATED()
        headers {
            contentType(applicationJson())
        }
        body([
            id: "auto-generated",
            status: "PENDING",
            createdAt: "2024-01-01T00:00:00Z"
        ])
        bodyMatchers {
            jsonPath('$.id', byRegex('[a-f0-9]{24}'))
            jsonPath('$.createdAt', byRegex('[0-9]{4}-[0-9]{2}-[0-9]{2}T[0-9]{2}:[0-9]{2}:[0-9]{2}Z'))
        }
    }
}
⚠ Avoid Hardcoding Dynamic Values
📊 Production Insight
I've seen contracts fail because the createdAt timestamp was one second off. Use byRegex to match the format, not the exact value.
🎯 Key Takeaway
Use body matchers for dynamic values to keep contracts stable.

Contract Testing for Message-Based Services

Spring Cloud Contract isn't just for HTTP; it supports messaging (e.g., RabbitMQ, Kafka). The concept is the same: consumer defines the expected message, producer verifies it can produce it.

Messaging contract example: ``groovy Contract.make { description "Payment processed event" label 'payment_processed' input { triggeredBy('processPayment()') } outputMessage { sentTo('payment.events') body([ transactionId: "txn-789", status: "COMPLETED" ]) headers { header('contentType', 'application/json') } } } ``

Here, triggeredBy calls a method that triggers the message. The producer must implement processPayment() in the test base class. The consumer uses the stub to simulate receiving the message.

Consumer test: ```java @SpringBootTest @AutoConfigureStubRunner(ids = "com.example:payment-service:+:stubs:8090") class PaymentEventListenerTest {

@Autowired private StubFinder stubFinder;

@Test void shouldReceivePaymentProcessedEvent() { // trigger the stub stubFinder.trigger("payment_processed"); // assert that the message was consumed } } ```

Messaging contracts are powerful but tricky. The producer must ensure the triggeredBy method is present in the base test class.

PaymentEventListenerTest.javaJAVA
1
2
3
4
5
6
7
8
9
10
11
12
13
14
@SpringBootTest
@AutoConfigureStubRunner(ids = "com.example:payment-service:+:stubs:8090")
class PaymentEventListenerTest {

    @Autowired
    private StubFinder stubFinder;

    @Test
    void shouldReceivePaymentProcessedEvent() {
        stubFinder.trigger("payment_processed");
        // verify that the event was processed
        // e.g., assert that a database record was updated
    }
}
💡Use Labels for Messaging Contracts
📊 Production Insight
In a Kafka setup, ensure the stub runner's messaging implementation matches your broker. I once debugged a test that passed locally but failed in CI because the CI used a different Kafka version.
🎯 Key Takeaway
Messaging contracts follow the same pattern but use triggeredBy and outputMessage.

What the Official Docs Won't Tell You

After years of using Spring Cloud Contract in production, here are the gotchas that the official documentation glosses over:

  1. Stub versioning is critical. The official docs show + for the version, which pulls the latest. In production, always pin to a specific version. Otherwise, a consumer's build can break unexpectedly when a new producer version is published. Use a property like payment-service.version and update it consciously.
  2. Contract priority matters. If you have multiple contracts for the same endpoint (e.g., different responses for different states), use the priority field. Higher priority contracts are matched first. Without priorities, the framework picks the first matching contract, which may not be the one you expect.
  3. Base class must be in a specific package. The generated tests look for the base class in the same package as the contracts (or a default). If you put it in a different package, the plugin won't find it. Use <basePackageForTests> in the plugin configuration to override.
  4. Stub runner restarts for each test class. By default, each test class gets a new WireMock server. This can slow down your test suite. Use @AutoConfigureStubRunner(stubsMode = StubRunnerProperties.StubsMode.LOCAL, ids = "...", port = "8090") to share a single port across tests, but be careful with state.
  5. Messaging contracts require extra configuration. For Kafka, you need to add spring-cloud-starter-contract-stub-runner-kafka and configure the embedded Kafka. The docs show a simple example, but in production you'll need to handle schema registry, Avro, or custom serializers.
  6. The plugin generates tests with a fixed naming convention. If you have multiple contracts for the same endpoint, the generated test names may collide. Use contractsDirectory to organize contracts in subdirectories, which generates separate test classes.
pom.xmlJAVA
1
2
3
4
5
6
7
8
9
10
11
<plugin>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-contract-maven-plugin</artifactId>
    <version>4.1.0</version>
    <extensions>true</extensions>
    <configuration>
        <baseClassForTests>com.example.BaseTestClass</baseClassForTests>
        <basePackageForTests>com.example.contracts</basePackageForTests>
        <contractsDirectory>src/test/resources/contracts</contractsDirectory>
    </configuration>
</plugin>
⚠ Pin Stub Versions in CI
📊 Production Insight
I once spent two days debugging a test that passed locally but failed in CI. The culprit: the CI had an older version of the stub cached in the local Maven repo. We fixed it by always running mvn clean and using -U to force snapshot updates.
🎯 Key Takeaway
Version stubs explicitly, use priorities, and configure the base class package correctly.

Integrating with CI/CD Pipeline

Spring Cloud Contract shines when integrated into your CI/CD pipeline. Here's the workflow I recommend:

  1. Producer pipeline:
  2. - Build and run unit tests.
  3. - Run contract verification (mvn clean install includes it).
  4. - If verification passes, publish the stubs to a repository (e.g., Artifactory). Use the Maven deploy plugin with classifier stubs.
  5. Consumer pipeline:
  6. - Build and run unit tests.
  7. - Run consumer tests against the latest stubs from the repository.
  8. - If tests pass, proceed to deployment.

Key configuration: ``xml <!-- Producer: publish stubs --> <plugin> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-contract-maven-plugin</artifactId> <configuration> <contractsMode>REMOTE</contractsMode> <baseClassForTests>com.example.BaseTestClass</baseClassForTests> </configuration> </plugin> ``

Consumer: use remote stubs ``java @AutoConfigureStubRunner( stubsMode = StubRunnerProperties.StubsMode.REMOTE, repositoryRoot = "https://repo.example.com/libs-release-local", ids = "com.example:payment-service:1.0.0:stubs:8090" ) ``

This setup ensures that consumers always test against the latest verified contracts. If a producer breaks a contract, the consumer's build fails before deployment.

Jenkinsfile.groovyJAVA
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
pipeline {
    agent any
    stages {
        stage('Build') {
            steps {
                sh 'mvn clean install'
            }
        }
        stage('Publish Stubs') {
            when { branch 'main' }
            steps {
                sh 'mvn deploy -DskipTests'
            }
        }
        stage('Consumer Tests') {
            parallel {
                stage('Billing Service') {
                    steps {
                        sh 'cd ../billing-service && mvn test'
                    }
                }
                stage('Notification Service') {
                    steps {
                        sh 'cd ../notification-service && mvn test'
                    }
                }
            }
        }
    }
}
💡Run Consumer Tests in Parallel
📊 Production Insight
We once had a pipeline where the producer published stubs even when contract verification failed. That defeated the purpose. Always gate stub publication on successful verification.
🎯 Key Takeaway
Automate stub publication and consumer testing in CI to catch breaking changes early.
● Production incidentPOST-MORTEMseverity: high

The Silent Breaking Change That Took Down Payments

Symptom
Billing service started throwing NullPointerException on payment callback. No alerts, just failed transactions.
Assumption
The billing team assumed the payment API hadn't changed because the endpoint URL was the same.
Root cause
Payment service renamed transactionId to txnId in the JSON response. No contract was in place, so the change went unnoticed.
Fix
Implemented Spring Cloud Contract with contracts for all payment callbacks. Producer now runs contract verification in CI, and consumer uses generated stubs in tests.
Key lesson
  • Always version your contracts — breaking changes require major version bumps.
  • Run contract verification as part of the producer's CI pipeline, not just locally.
  • Consumers should test against the latest published stubs, not a shared test environment.
  • Automate stub publication to a repository (e.g., Artifactory) to ensure consistency.
  • Don't rely on endpoint URLs alone; the contract defines the API contract, not the URL.
Production debug guideSymptom to Action4 entries
Symptom · 01
Contract verification fails locally but passes in CI
Fix
Check for environment-specific differences: JDK version, Spring Boot version, or missing stubs in local Maven repo.
Symptom · 02
Consumer test passes against stub but fails against real producer
Fix
Stub may be outdated. Verify that the stub was generated from the latest contract version. Regenerate and republish.
Symptom · 03
Multiple contracts for same endpoint cause conflicts
Fix
Ensure each contract has a unique name or priority. Use priority to override base contracts.
Symptom · 04
Stub not found in repository
Fix
Check that the producer published the stub correctly. Verify the groupId, artifactId, and version match the consumer's dependency.
★ Quick Debug Cheat SheetCommon issues and immediate actions for Spring Cloud Contract
Contract verification test fails with 'no matching request'
Immediate action
Check the request matchers in the contract. Ensure method, path, headers, and body match exactly.
Commands
mvn spring-cloud-contract:convert -DskipTests
mvn test -Dspring.cloud.contract.verifier.skip=false
Fix now
Update the contract to match the actual request or fix the producer code.
Stub download fails with 404+
Immediate action
Verify the stub artifact exists in the repository with the correct coordinates.
Commands
curl -u user:pass https://repo.example.com/libs-release-local/com/example/payment-stubs/1.0.0/payment-stubs-1.0.0-stubs.jar
mvn dependency:get -Dartifact=com.example:payment-stubs:1.0.0:jar:stubs
Fix now
Publish the stubs from the producer build or update the consumer's dependency version.
Consumer test uses wrong stub version+
Immediate action
Check the consumer's pom.xml for the stub dependency version. Ensure it matches the producer's contract version.
Commands
mvn dependency:tree | grep stubs
mvn versions:display-dependency-updates
Fix now
Update the stub version in pom.xml to the latest published version.
FeatureSpring Cloud ContractWireMockPact
Consumer-driven contractsYesNoYes
Producer verificationYes (auto-generated tests)NoYes (separate tool)
Stub generationYesYes (manual)Yes
Messaging supportYesNoYes
Language supportJava, GroovyJava, otherMultiple
⚙ Quick Reference
6 commands from this guide
FileCommand / CodePurpose
contract.groovyContract.make {What is Consumer-Driven Contract Testing?
BaseTestClass.java@SpringBootTestSetting Up Spring Cloud Contract
payment_contract.groovyContract.make {Writing Contracts with Groovy DSL
PaymentEventListenerTest.java@SpringBootTestContract Testing for Message-Based Services
pom.xmlWhat the Official Docs Won't Tell You
Jenkinsfile.groovypipeline {Integrating with CI/CD Pipeline

Key takeaways

1
Consumer-driven contract testing ensures API compatibility between services without end-to-end tests.
2
Spring Cloud Contract generates producer tests and consumer stubs from Groovy or Java DSL contracts.
3
Always pin stub versions in CI, use body matchers for dynamic values, and configure the base class package.
4
Integrate contract verification and stub publication into your CI/CD pipeline for early detection of breaking changes.
INTERVIEW PREP · PRACTICE MODE

Interview Questions on This Topic

Q01SENIOR
Explain how Spring Cloud Contract generates tests from a Groovy contract...
Q02SENIOR
What are the trade-offs of using consumer-driven contract testing vs. en...
Q03SENIOR
How would you debug a situation where a consumer test passes against a s...
Q01 of 03SENIOR

Explain how Spring Cloud Contract generates tests from a Groovy contract.

ANSWER
The Spring Cloud Contract Maven plugin reads Groovy (or Java) contract files from src/test/resources/contracts/. For each contract, it generates a JUnit test class in target/generated-test-sources/contracts/. The test uses RestAssured to send a request matching the contract's request part and asserts that the response matches the contract's response part. The base test class provides the Spring context.
FAQ · 4 QUESTIONS

Frequently Asked Questions

01
What is the difference between Spring Cloud Contract and WireMock?
02
Can I use Spring Cloud Contract with asynchronous messaging?
03
How do I handle versioning of contracts?
04
What if I have multiple consumers with different needs?
N
Naren Founder & Principal Engineer

20+ years shipping production Java in banking & fintech. Notes here come from systems that actually shipped.

Follow
Verified
production tested
July 15, 2026
last updated
2,398
articles · all by Naren
🔥

That's Spring Cloud. Mark it forged?

4 min read · try the examples if you haven't

Previous
Introduction to Spring Cloud Task: Short-Lived Microservices and Batch Jobs
12 / 34 · Spring Cloud
Next
Spring Cloud Bus: Broadcasting Configuration Changes and Events Across Microservices