Verifying API Compatibility Between Services
When code changes in a microservice, the most dangerous regressions happen not inside the service but in how it communicates with other services. A service that compiles, starts, and passes its unit tests can still break production by sending a different payload, dropping a header, or changing an error response format.
BitDive detects these regressions because execution traces capture the full HTTP exchange between services: not just the fact that a call happened, but exactly what was sent and what was received.
The Problem: Silent API Drift
Traditional testing is structurally blind to inter-service API drift:
| Testing approach | What it catches | What it misses |
|---|---|---|
| Unit tests | Logic errors inside the service | Whether the outgoing HTTP request is still compatible |
| Contract tests (Pact) | Predefined contract violations | Changes not covered by examples (serialization, headers, error bodies) |
| Integration tests | Full internal chain behavior | Downstream API compatibility after infrastructure changes |
| OpenAPI validation | Schema violations | Actual runtime serialization differences |
The regressions that slip through usually have the same pattern: the service still works locally, but the actual bytes it sends over HTTP have changed.
Common causes:
- Jackson or ObjectMapper configuration change
- DTO field rename or removal
Include.NON_NULLtoggledWRITE_DATES_AS_TIMESTAMPSenabled- MapStruct or ModelMapper update
- Spring Boot version bump changing default serialization
- Feign or WebClient interceptor modification
- Auth header propagation logic change
What BitDive Captures in Each HTTP Exchange
When a service makes an outgoing HTTP call during a traced execution, BitDive records:
| Layer | Captured data |
|---|---|
| Endpoint | HTTP method, full URL, path variables, query parameters |
| Request headers | All headers including Authorization, Content-Type, X-Correlation-Id, tenant headers, feature flags |
| Request body | The actual serialized JSON/XML as sent over the wire |
| Response status | HTTP status code |
| Response headers | All response headers |
| Response body | The actual response payload as received |
| Timing | Duration, timestamp |
| Error details | Exception class, message, stack trace (if the call failed) |
The key point: this is the actual runtime exchange, not the object before serialization or a theoretical schema. If Jackson serializes a LocalDate as "2026-02-28" in the baseline but as 1740700800000 after a config change, that difference is visible in the trace.
How Before/After Comparison Detects API Drift
BitDive's core mechanism for detecting API regression is the same before/after trace comparison used for all deterministic verification:
Step 1: Establish a Baseline
Trigger a business scenario via a real API call (curl, Postman, your frontend, an automated script). BitDive's agent captures a trace that includes all outgoing HTTP calls with full payload details.
This trace is the runtime API contract: it represents how the service actually interacted with its dependencies at this point in time.
Step 2: Make a Code Change
Refactor business logic, upgrade a library, update a DTO, change ObjectMapper settings, bump Spring Boot version.
Step 3: Trigger the Same Scenario Again
Call the same endpoint on the updated service. BitDive's agent captures a new trace.
Step 4: Compare
BitDive performs a diff across the two traces, comparing every layer of each outgoing HTTP call:
- Endpoint diff: Did the URL, method, or query parameters change?
- Header diff: Were headers added, removed, or modified?
- Request body diff: Did the serialized JSON change? Which fields are different?
- Response status diff: Did the downstream return a different status code?
- Response body diff: Did the response structure or content change?
- Sequence diff: Did the order, count, or presence of API calls change?
Examples of Regressions BitDive Detects
Serialization Change After Jackson Upgrade
Before:
{
"createdDate": "2026-02-28",
"status": "ACTIVE",
"amount": "19.99"
}
After (Jackson config changed):
{
"createdDate": 1740700800000,
"status": 0,
"amount": 19.99
}
Unit tests pass. Contract test passes (it only checked that status field exists). BitDive flags the diff: date format changed, enum serialization changed, number type changed.
Missing Header After Interceptor Refactoring
Before: Outgoing request includes X-Correlation-Id: abc-123 and Authorization: Bearer ...
After: Interceptor was refactored. X-Correlation-Id is still sent, but Authorization header is missing for one specific downstream call.
No test covers this header on this specific call path. BitDive shows the header diff immediately.
Changed Error Contract
Before: Downstream returns 404 with body:
{"code": "CUSTOMER_NOT_FOUND", "message": "Customer 42 not found"}
After: Downstream returns 500 with plain text: Internal Server Error
The upstream service's error handler expects a JSON body with a code field. It now throws a deserialization exception. BitDive detects both the status code change and the response body format change.
Altered Call Sequence
Before: The service calls GET /accounts/42 first, then POST /transactions with account data.
After refactoring: The service calls POST /transactions without calling /accounts/42 first.
Contract tests for each endpoint pass individually. BitDive detects that the call sequence changed: one call disappeared and the remaining call now sends different data.
Capturing Traces for API Verification
API verification requires two traces captured from real API calls: one before the change, one after.
The typical workflow:
- Run the service with BitDive agent attached.
- Trigger the business scenario (e.g.
curl -X POST /api/orders -d '{...}'). - BitDive captures the baseline trace.
- Make the code change and restart the service.
- Trigger the same scenario again.
- BitDive captures a new trace.
- Compare the two traces to detect API drift.
This is different from integration testing with ReplayTestBase, which replays recorded dependencies inside JUnit tests. API verification compares traces captured from live service execution, so it operates on actual serialized HTTP exchanges.
For a real example of before/after trace comparison, see the Interactive Demo with Cursor (video), where an AI agent compares two traces layer-by-layer after a code change.
What Counts as a Breaking API Change
Not every difference in a trace is a regression. BitDive distinguishes between expected and unexpected changes:
| Change type | Typically acceptable | Typically breaking |
|---|---|---|
| New field in response | ✅ Optional field added | ❌ Required field removed |
| Header change | ✅ New trace header added | ❌ Auth or content-type header removed |
| Status code | ❌ Any status code change (200→404, 404→500) | |
| Serialization format | ❌ Date, enum, or number format changed | |
| Request body | ✅ New optional field added | ❌ Required field missing or renamed |
| Call sequence | ✅ Same calls, minor reordering | ❌ Call removed or new unexpected call |
| Timestamp/UUID | ✅ Expected to differ per execution |
Fields like requestId, timestamp, traceId, and UUID are automatically treated as noise and excluded from comparison. See the Configuration Guide for custom masking and comparison policies.
CI/CD Integration
API verification fits naturally into the delivery pipeline:
- Baseline traces exist from captured production or staging scenarios.
- Developer pushes a change.
mvn testruns the replay tests (which include API verification).- If an outgoing HTTP exchange differs from the baseline, the test fails.
- CI reports which endpoint, header, or payload changed.
- Team decides: expected change (update baseline) or regression (fix the code).
This is especially effective during:
- Library upgrades (Jackson, Spring Boot, MapStruct)
- Refactoring integration layers
- DTO and mapper changes
- AI-assisted code modifications verified via MCP
Related
- Integration Testing with Deterministic Replay — Full-context Spring Boot testing with trace replay
- Testcontainers Integration Tests — Real database with stubbed APIs
- Before/After Trace Comparison — Core mechanism for detecting changes
- BitDive vs. Contract Testing (Pact) — How trace comparison complements static contracts
- Distributed Tracing — Service-level trace capture and visualization
- CI/CD Integration — Automating regression checks in your pipeline