When an enum change breaks clients
Removing an enum value breaks clients that send it, because the server starts rejecting their requests. Adding an enum value can break clients that receive it, because code written for the old list may not handle a value it has never seen. The reverse cases, adding to a request enum or removing from a response enum, are usually safe for parsing.
An enum here means a field restricted to a fixed list of values, such as an order status of pending, paid or shipped. Most of the risk sits in responses, and generated client code makes it worse.
Adding and removing values in each direction
In a request, such as a status filter on GET /orders, the server validates the value. Adding archived to the allowed list rejects nothing that worked before. Removing pending rejects every client that still sends it, usually with a 400.
In a response, the client interprets the value. Adding on_hold to an order's status means old clients may receive a string they can't map to anything. Removing a value from a response enum doesn't break parsing, but code that waits for that state will never see it.
Why generated clients fail on new values
Code generators usually turn an OpenAPI enum into a native enum type. What happens with an unknown value then depends on the language, the library and the generator's settings:
- In Swift, a String-backed enum decoded with Codable throws a DecodingError for an unknown value, which can fail decoding of the whole response.
- In Java, Jackson throws on an unknown enum value by default, unless READ_UNKNOWN_ENUM_VALUES_AS_NULL or READ_UNKNOWN_ENUM_VALUES_USING_DEFAULT_VALUE is enabled.
- In TypeScript, a union type disappears at runtime, so the unknown string passes through and lands in whatever code handles unmatched cases.
- A switch that lists every known case with no default has no path for a new value. Where the compiler checks exhaustiveness, regenerating the SDK turns the new value into a compile error: safer than a crash, but still a break for anyone upgrading.
Defensive patterns for API clients
Treat every enum in a response as open, even when the spec lists its values. The Swift snippet shows the first habit in its simplest form.
- Map unrecognized values to an explicit unknown case and keep the raw string for logging.
- Give every switch on a response enum a fallback that does something safe, such as showing a generic label or hiding an action.
- Turn on your generator or serializer option for unknown values if it has one. Some generators can add a fallback case to every enum.
- Don't assume a state will always arrive. A value removed from a response enum can leave code waiting indefinitely.
enum OrderStatus: String, Decodable {
case pending, paid, shipped
case unknown
init(from decoder: Decoder) throws {
let raw = try decoder.singleValueContainer().decode(String.self)
self = OrderStatus(rawValue: raw) ?? .unknown
}
}Defensive patterns for API providers
Providers can make new values cheaper for everyone by planning for them:
- Document which enums may grow. State in the description that new values can appear and that clients must handle unknown ones.
- OpenAPI has no standard keyword for an open enum. Some style guides use a vendor extension such as x-extensible-enum, and some schemas pair the known values with a plain string type using anyOf.
- Ship client support first. Release SDK versions with an unknown fallback before the server starts sending the new value.
- Gate new values when old clients cannot cope. Send them only to clients on a newer API version, and map them to the closest old value for everyone else.
- Keep accepting a removed request value for a deprecation period, mapping it to its replacement, before rejecting it.
- Flag enum changes in contract review, so adding a response value is a deliberate decision.