How do you handle backward-incompatible changes to a public REST API in a Spring Boot service used by other teams?

8 minintermediateapi-versioningbackward-compatibilitybehavioral

Quick Answer

Prefer additive, backward-compatible changes whenever possible (adding a new optional field rather than removing/renaming an existing one) since they require no coordination at all. When a genuinely breaking change is unavoidable, introduce it behind a new API version (see the API-versioning question) rather than modifying the existing contract in place, communicate the change and a deprecation timeline to consuming teams well in advance, keep the old version running and functional throughout a real migration window, monitor actual usage of the deprecated version to know when it's safe to retire, and only remove it once consumers have genuinely migrated (or the deprecation deadline has passed with clear prior communication) rather than on an arbitrary internal schedule.

Detailed Answer

Breaking a contract other teams depend on is one of the more disruptive things an API-owning team can do if handled carelessly — a deliberate, communicative process matters as much as the technical versioning mechanism itself.

1. Prefer additive, non-breaking changes whenever the requirement genuinely allows it. Adding a new optional field to a response, adding a new endpoint, or adding a new optional request parameter with a sensible default typically requires zero coordination with consuming teams at all, since well-behaved clients (deserializing into a DTO that ignores unknown fields, for instance) are unaffected. This should always be the first option considered before reaching for a breaking change and a new version.

2. When a genuine breaking change is unavoidable (removing a field, changing a field's type or meaning, changing an endpoint's behavior in an incompatible way), introduce it behind a new API version (see the API-versioning question — URI versioning being the most common, most discoverable approach) rather than modifying the existing, already-depended-upon contract in place.

3. Communicate proactively, well before the change ships. Consuming teams should hear about a planned breaking change and its migration deadline before it happens, not discover it via a failing integration test or a production incident — this might mean an internal API changelog, a direct message to known consumer teams, or (for external/public APIs) a formal deprecation notice with a concrete sunset date.

4. Run both versions in parallel during a genuine migration window. The old version should stay fully functional (not degraded or "soft-deprecated" in some broken state) for long enough that consuming teams can realistically plan and execute their own migration on their own schedule, not an artificially rushed one dictated purely by the API team's convenience.

5. Monitor actual usage of the deprecated version (request counts/logs tagged by API version, or a deprecation warning header returned to callers still using the old version) to know, with real data, whether it's actually safe to retire — rather than guessing or assuming migration is complete.

6. Only remove the old version once consumers have genuinely migrated, or the previously-communicated deadline has passed with clear, repeated advance notice — removing a still-actively-used old version on an arbitrary internal timeline, regardless of what was actually communicated, is exactly the kind of action that erodes other teams' trust in the API team's reliability going forward.

What this communicates in an interview: treating API compatibility as a cross-team communication and trust problem, not just a technical versioning mechanism — the versioning scheme itself (covered in the API-versioning question) is necessary but not sufficient without the surrounding process.