Spring Cloud Contract for CDC Tests Between Services
Consumer-driven contracts that catch integration breaks at build time — before either service deploys.
Service A calls Service B’s API. Service B changes a response field from string to integer. Service B's tests pass. Service A's tests pass (they mock Service B). Both deploy. Service A crashes in production parsing the response.
This is the contract gap. Each service tests in isolation against its own assumptions about the other. Nobody verifies those assumptions match reality until production traffic proves otherwise.
Spring Cloud Contract closes this gap by making the contract a shared, testable artifact that both sides verify independently.
The contract
A contract is a Groovy or YAML file that defines: given this request, expect this response. It lives in the producer’s repository (Service B) but describes what the consumer (Service A) expects.
Contract.make {
description "GET contract by ID returns full details"
request {
method GET()
url "/api/contracts/123"
headers {
header("Authorization", "Bearer token")
}
}
response {
status 200
headers {
contentType applicationJson()
}
body([
id: 123,
status: "ACTIVE",
amount: 5000.00,
effectiveDate: "2024-01-15"
])
}
}Two-sided verification
Producer side: Spring Cloud Contract generates a JUnit test from the contract. The test starts the producer’s Spring context, sends the request defined in the contract, and asserts the response matches. If Service B changes the response shape, this generated test fails in Service B’s CI pipeline.
Consumer side: The contract generates a WireMock stub JAR. Service A includes this stub as a test dependency. Service A’s integration tests run against the stub. If Service A expects a field that the contract doesn’t define, the test fails in Service A’s CI pipeline.
Get Basuki Nath’s stories in your inbox
Join Medium for free to get updates from this writer.
The workflow
- Consumer team writes a contract describing what they need (or producer team writes it based on their API spec)
- Contract lives in the producer’s repo under
src/test/resources/contracts/ - Producer’s CI generates tests from contracts — verifies the producer still satisfies them
- Producer’s CI publishes stub JARs to a Maven repository
- Consumer’s CI pulls the latest stub JAR and runs integration tests against it
- If either side drifts, their own build breaks — not the other team’s
No shared test environment needed. No end-to-end tests that require both services running simultaneously. Each team gets fast, independent feedback about contract compliance.
Where it breaks down
Schema evolution: adding a new optional field to the response doesn’t break the contract (consumers ignore unknown fields). But removing a field or changing a type does. Contract-first design forces you to think about backward compatibility before making changes — which is exactly the point.
Contract proliferation: with 10 consumers of the same API, you might have 10 contracts covering different subsets of the response. Keeping them deduplicated and organized requires discipline. We group contracts by consumer name and use shared fragments for common response structures.
Async events: Spring Cloud Contract supports messaging contracts (Kafka, SQS, RabbitMQ), not just HTTP. But the semantics are different — you’re verifying message shape, not request-response pairs. The tooling works but feels more awkward than the HTTP path.
When to invest
Contract tests earn their setup cost when: two or more teams own services that communicate, releases are independent (not deployed as a monolith), and integration bugs have historically reached production.
For a single team owning both producer and consumer, the overhead isn’t justified — they can run both services in integration tests directly. For a team of 3 services with infrequent API changes, a shared Postman collection might be enough.
The sweet spot: 5+ services, 3+ teams, weekly releases, and a history of “it worked in staging but broke in prod because the other service deployed a breaking change.” That’s where contract tests prevent real money lost to production incidents.
The contract isn’t bureaucracy. It’s the agreement that exists whether you write it down or not. Spring Cloud Contract just makes it executable — so both sides discover violations in CI instead of in production at 2am.
Getting started without boiling the ocean
Pick your most-broken integration — the one that caused the last production incident from a mismatched API assumption. Write one contract for the endpoint that broke. Set up the producer verification (5 minutes if you’re already on Spring Boot). Generate the stub JAR. Have the consumer team run one test against it.
That single contract, covering one endpoint, exercised by both sides, will catch the next breaking change to that specific integration. You don’t need full coverage to get value. You need coverage on the paths that have historically broken.
Once the first contract catches a real break in CI (and it will, usually within a month), adoption sells itself. Teams that experienced the “caught before prod” moment become advocates. Teams that haven’t yet are skeptical. That’s normal. Let the evidence accumulate.








