Consumer Driven Contracts
Consumer-Driven Contract (CDC) is a collaborative pattern used to ensure that two services (a Provider and a Consumer) can communicate successfully, even as they evolve independently.
Get Kandaanusha’s stories in your inbox
Join Medium for free to get updates from this writer.
In the traditional world, the Provider (the API owner) dictates the schema. In CDC, the Consumer tells the Provider: “Here is exactly what I need from you to function.”
The “Gold” Rule of CDC
Only specify what you actually use.
If a provider returns 50 fields but your consumer only needs id and email, your contract should only mention those two. This allows the provider to delete or change the other 48 fields without breaking your build, maximizing evolutionary architecture.
Popular Tools
If you’re looking to implement this, these are the industry standards:
- Pact: The most popular framework. It supports multiple languages (Java, JS, Python, Go, etc.) and uses a “Pact Broker” to share contracts between teams.
- Spring Cloud Contract: A powerhouse for the Java/Spring ecosystem that integrates deeply with WireMock.
Pact
To implement Consumer-Driven Contracts (CDC) in Java, the most common tool is Pact. It allows the consumer to define their requirements in code, which then generates a “contract” (JSON file) that the provider must satisfy.
1. The Consumer Side (Defining the Need)
The consumer writes a test that defines the expected JSON structure. This test acts as a “mock” for the consumer’s unit tests, but it also generates the contract file.
@ExtendWith(PactConsumerTestExt.class)
@PactTestFor(providerName = "UserService")
public class UserContractTest {
@Pact(consumer = "OrderService")
public RequestResponsePact createPact(PactDslWithProvider builder) {
return builder
.given("User 123 exists")
.uponReceiving("A request for User 123")
.path("/users/123")
.method("GET")
.willRespondWith()
.status(200)
.body(new PactDslJsonBody()
.stringType("id", "123")
.stringType("email") // Only checks that email is a String
.integerType("age", 25)
.close())
.toPact();
}
@Test
@PactTestFor
void testGetUser(MockServer mockServer) {
// Execute the real Java call against the mockServer
// This ensures your Java DTOs can actually parse the defined contract
}
}2. The Pact Broker (The Handshake)
Once the test passes, a .json contract file is generated. In a CI/CD pipeline, you upload this to a Pact Broker.
3. The Provider Side (The Verification)
The Provider (the service sending the JSON) pulls the contract from the broker and runs it against their actual controller. If they renamed email to user_email, this test will fail in their build pipeline.
@Provider("UserService")
@PactBroker(url = "https://your-broker-url.com")
public class PactProviderTest {
@TestTemplate
@ExtendWith(PactVerificationInvocationContextProvider.class)
void pactVerificationTestTemplate(PactVerificationContext context) {
context.verifyInteraction();
}
@BeforeEach
void before(PactVerificationContext context) {
// Point to your running local server or use a MockMVC target
context.setTarget(new HttpTestTarget("localhost", 8080));
}
}How this handles Evolution in CI/CD
- Breaking Change Prevention: If a developer on the Provider team deletes a field that the Consumer still has in their contract, the Provider’s CI build fails immediately.
- Safe Deletion: The Provider can see exactly which fields are not in any consumer contracts. If a field isn’t in a contract, it is safe to delete.
- Independent Deployments: Since the contract is verified during the build, you don’t need to coordinate deployments. If the build passes, you know the services are compatible.
Summary Checklist for Implementation
- Identify Consumers: Who is calling your API?
- Write Pacts: Focus only on the fields you actually use (The Tolerant Reader).
Automate in CI:
- Consumer CI: Runs tests -> Publishes Pact.
- Provider CI: Pulls Pact -> Verifies -> Deploys only if “Can-I-Deploy” check passes.



