How do messaging contracts and stub distribution work, and how do you fit Spring Cloud Contract into a CI/CD pipeline?
answer
- input triggeredBy / outputMessage sentTo
- StubTrigger.trigger(label) on consumer
- verify contracts → publish versioned stubs JAR → consumers resolve
- ownership: producer repo / shared repo / consumer PR
- pin version in release, '+' in early-warning job
basics
~20 sContracts also cover messaging: they describe an input trigger and an output message on a channel. The producer's build verifies it publishes the right message; consumers use StubTrigger to fire it. In CI, the producer must verify contracts and publish the stubs JAR before consumers pull it, so stub distribution and versioning must be governed.
solid answer
~40 sBeyond HTTP, contracts describe messaging: a label-triggered input and an outputMessage with destination, headers, and body, integrated with Spring Cloud Stream / Spring Integration / test binders. The producer's generated test asserts that invoking the trigger actually emits the contracted message; consumers use StubTrigger.trigger(label) to simulate the producer emitting it and verify their listener. For CI/CD the sequencing is the key design concern: the producer pipeline must verify contracts (generated tests) and only then publish the versioned stubs JAR to Nexus/Artifactory; consumers resolve it via Stub Runner REMOTE mode. You decide contract ownership (producer repo vs shared repo vs consumer PRs), version pinning vs '+', and whether to run consumer tests against latest producer stubs to catch breakage early. Done well it replaces brittle end-to-end suites with fast, decoupled, independently-deployable verification.
code
groovy · 18 lines// Messaging contract: producer emits an event when trigger fires
Contract.make {
label 'user_created' // consumer references this label
input {
triggeredBy('createUser()') // base-class method that sends
}
outputMessage {
sentTo 'user-events' // destination/topic
headers { header('type', 'USER_CREATED') }
body([ id: 42, name: 'Ada' ])
bodyMatchers { jsonPath('$.id', byType()) }
}
}
// Consumer test fires the stub by label and verifies its listener:
// @Autowired StubTrigger stubTrigger;
// stubTrigger.trigger("user_created");
// // then assert the consumer's @StreamListener/@KafkaListener handled itgo deeper
Know that contracts can also describe messages, not just HTTP.
Describe input/outputMessage and that consumers use StubTrigger to fire message stubs.
Explain stub distribution via artifact repo, REMOTE Stub Runner, and version pinning trade-offs.
Design contract ownership, CI sequencing (verify → publish → consume), drift-detection jobs, versioning-as-public-API, and polyglot participation via Docker/Stub Runner Boot; compare with Pact.
## Messaging contracts Spring Cloud Contract isn't HTTP-only. A **messaging contract** describes asynchronous behavior: - `input` — how the producer is stimulated: either `triggeredBy('methodName()')` (a base-class method that causes a send) or an incoming `messageFrom('channel')`. - `outputMessage` — the expected emitted message: `sentTo('destination')`, `headers { ... }`, and `body([...])` (with the same matcher facilities as HTTP). It integrates with **Spring Cloud Stream**, **Spring Integration**, Apache Camel, or plain **spring-cloud-contract-verifier** test binders. On the producer, the generated test invokes the trigger and asserts the right message lands on the destination. On the consumer, you inject **StubTrigger** and call `stubTrigger.trigger("label")` (the contract's label) to make Stub Runner emit the contracted message, then assert your listener handled it. ## Stub distribution The producer build packages a **stubs JAR** (classifier `stubs`) containing WireMock mappings (HTTP) and/or messaging stub definitions. Distribution options: - Publish to a **binary repo** (Nexus/Artifactory); consumers use Stub Runner `REMOTE` with `repositoryRoot`. - Bundle on the **classpath** (`CLASSPATH` mode) via a dependency. - Alternatively use a **Stub Runner Boot** server or Docker image to serve stubs to non-JVM consumers, and the **contract Docker image** to verify non-JVM producers. ## Contract ownership models - **Producer repo** (default): contracts under `src/test/resources/contracts`, producer publishes stubs. Simple, but consumers can't easily contribute needs. - **External contracts repo**: a shared git repo of contracts; producer pulls them to verify, publishes stubs. Central governance. - **Consumer contributes via PR** to the producer: truest 'consumer-driven' — consumer proposes the contract, producer must make it pass before merge. ## CI/CD sequencing (the principal concern) 1. Producer pipeline runs the generated contract tests. If any fail, the API broke the contract — stop. 2. On success, publish the **versioned** stubs JAR to the artifact repo. 3. Consumer pipelines resolve stubs via Stub Runner. Choice: pin an exact producer version (deterministic, but you find breakage late) vs. `+`/latest (catch breakage early, but non-deterministic builds). 4. Optionally run a scheduled 'consumer against latest producer stubs' job to detect drift before release. ## Versioning & compatibility - Treat contracts as part of the producer's public API; breaking a contract = major-version concern. - Keep old contracts around while consumers migrate; add new contracts for new behavior rather than mutating existing ones. - Because stubs are versioned artifacts, you can test a consumer against a specific historical producer version. ## Gotchas - If the stubs JAR isn't published before consumers run (ordering bug), consumer builds fail to resolve. - `+`/latest makes builds non-reproducible; pin in release pipelines, use latest in a separate early-warning job. - Messaging contracts need the right binder/test config in the base class or they won't trigger. - Contract drift: un-contracted fields give false confidence; encourage consumers to contract what they actually rely on. ## When to use / limits Great for internal service meshes with multiple JVM (and, via Docker, non-JVM) services and independent deployment. Less suited to third-party APIs you don't control, or ultra-stable public contracts already governed by OpenAPI + governance process. Compare with **Pact** (broker-centric, polyglot-first) — SCC is more Spring/JVM-native and build-plugin-driven.
- How would you avoid non-reproducible builds while still catching producer-consumer drift early?Pin exact producer stub versions in the consumer's release pipeline for reproducibility, and run a separate scheduled/nightly job that resolves the producer's latest ('+') stubs; that job surfaces drift early without making the deterministic release build depend on latest.
- How can non-JVM consumers or producers participate in Spring Cloud Contract?Use the Stub Runner Boot server / Docker image to serve stubs over HTTP to any-language consumers, and the Spring Cloud Contract Docker image to run contract verification against a non-JVM producer's running endpoint, keeping contracts language-agnostic.
saying these in an interview costs you the question
- Assuming SCC is HTTP-only and can't do messaging/event contracts.
- Publishing stubs before (or without) verifying the contracts pass.
- Mutating existing contracts in place instead of versioning, breaking consumers pinned to older stubs.
- Using '+'/latest everywhere and then being surprised by non-reproducible builds.