Versioning your APIs

Microservices · lesson 14 of 33 · 4 min read

Change a contract without breaking the callers you cannot redeploy at the same moment.

Open this lesson in the learning hub

Key points

  • You never deploy everything at once. Old and new callers run side by side during every rollout, so plan for both.
  • Prefer additive changes. A new optional field breaks nobody; removing or renaming one breaks everybody.
  • Consumers must ignore unknown fields. Spring Boot disables FAIL_ON_UNKNOWN_PROPERTIES for you - plain Jackson enables it, so leave Boot alone.
  • When you must break, run /v1 and /v2 side by side and publish a sunset date you actually enforce.
  • Events need the same discipline. Use a schema registry (Avro or Protobuf) and only make backward-compatible schema changes.
  • Spring Framework 7 (Spring Boot 4) adds built-in API version routing, worth a look if you are already on it.

Example

// Additive change: v2 adds fields, v1 keeps its exact old shape.
record OrderV1(String id, String status, long totalCents) {}
record OrderV2(String id, String status, long totalCents, String currency, Instant placedAt) {}

@RestController
@RequestMapping("/v1/orders")
class OrderControllerV1 {

    @GetMapping("/{id}")
    ResponseEntity<OrderV1> get(@PathVariable String id) {
        return ResponseEntity.ok()
                // RFC 8594: tell clients exactly when this endpoint dies.
                .header("Sunset", "Wed, 31 Dec 2026 23:59:59 GMT")
                .body(orders.viewV1(id));
    }
}

@RestController
@RequestMapping("/v2/orders")
class OrderControllerV2 {

    @GetMapping("/{id}")
    OrderV2 get(@PathVariable String id) {
        return orders.viewV2(id);
    }
}

Add fields freely, remove them slowly, and never break a caller without a dated warning.

This is a reading copy. The full lesson — with the visual explainer, the interactive lab and a Run button for the code — lives in the Microservices course, and every lesson in it is listed on the Microservices contents page.