This page provides more detailed explanations for the list of breaking and non-breaking changes given in the Versioning section.

It's not always absolutely clear what counts as a breaking (incompatible) change. The guidance here should be treated as indicative rather than a comprehensive list of every possible change.

The rules listed here are only concerned with client compatibility. It is expected that API producers are aware of their own requirements with respect to deployment, including changes in implementation details.

The general aim is that clients should not be broken by a service updating to a new minor version or patch. The kinds of breakages under consideration are:

  • Source compatibility: code written against 1.0 failing to compile against 1.1
  • Binary compatibility: code compiled against 1.0 failing to link/run against a 1.1 client library. (Exact details depend on the client platform; there are variants of this in different situations.)
  • Wire compatibility: an application built against 1.0 failing to communicate with a 1.1 server
  • Semantic compatibility: everything runs but yields unintended or surprising results

To put it another way: old clients should be able to work against newer servers within the same major version number, and when they want to update to a new minor version (for example to take advantage of a new feature) they should be able to do so easily.

Note: Where we refer to version numbers such as v1.1 and v1.0, we are referring to logical version numbers that are never made concrete. They are purely for the sake of making it easier to describe changes.

As well as theoretical, protocol-based considerations, there are practical considerations due to the existence of client libraries involving both generated code and hand-written code. Wherever possible, test changes you're considering by generating new versions of client libraries and making sure their tests still pass.

The discussion below divides proto messages into three categories:

  • Request messages (such as GetBookRequest)
  • Response messages (such as ListBooksResponse)
  • Resource messages (such as Book, and including any messages which are used within other resource messages)

These categories have different rules, as request messages are only ever sent from the client to the server, response messages are only ever sent from the server to the client, but typically resource messages are sent both ways. In particular, resources that can be updated need to be considered in terms of a read/modify/write cycle.

Backwards-compatible (non-breaking) changes

Adding an API interface to an API service definition

From a protocol perspective, this is always safe. The only caveat is that client libraries may have already used your new API interface name within hand-written code. If your new interface is entirely orthogonal to existing ones, this is unlikely; if it's a simplified version of an existing interface, that's more likely to cause a conflict.

Adding a method to an API interface

Unless you add a method which conflicts with a method which is already being generated in client libraries, this should be fine.

(Example where it could be breaking: if you have a GetFoo method, the C# code generator will already create GetFoo and GetFooAsync methods. Adding a GetFooAsync method in your API interface would therefore be a breaking change from a client library perspective.)

Adding an HTTP binding to a method

Assuming the binding doesn't introduce any ambiguities, making the server respond to a URL it previously would have rejected is safe. This may be done when making an existing operation apply to a new resource name pattern.

Adding a field to a request message

Adding request fields can be non-breaking, so long as clients that don't specify the field will be treated the same in the new version as in the old version.

The most obvious example of where this can be done incorrectly is with pagination: if v1.0 of the API does not include pagination for a collection, it cannot be added in v1.1 unless the default page_size is treated as infinite (which is generally a bad idea). Otherwise, v1.0 clients who expect to get complete results from a single request could receive truncated results, with no awareness that the collection contains more resources.

Adding a field to a response message

A response message that is not a resource (e.g. a ListBooksResponse) can be expanded without breaking clients, so long as this does not change the behavior of other response fields. Any field that was previously populated in a response should continue to be populated with the same semantics, even if this introduces redundancy.

For example, a query response in 1.0 might have a boolean field of contained_duplicates to indicate that some results were omitted due to duplication. In 1.1, we might provide more detailed information in a duplicate_count field. Even though it's redundant from a 1.1 perspective, the contained_duplicates field must still be populated.

Adding a value to an enum

An enum that is only used in a request message can freely be expanded to include new elements. For example, using the Resource View pattern, a new view can be added in a new minor version. Clients never need to receive this enum, so they don't have to be aware of values that they don't care about.

For resource messages and response messages, the default assumption is that clients should handle enum values they're not aware of. However, API producers should be aware that writing applications to correctly handle new enum elements may be difficult. API owners should document expected client behavior when encountering an unknown enum value.

Proto3 allows clients to receive a value they are unaware of and reserialize the message maintaining the same value, so this does not break the read/modify/write cycle. The JSON format allows a numeric value to be sent where the "name" for the value is unknown, but the server will typically not know whether or not the client actually knows about a particular value. JSON clients may therefore be aware that they have received a value that was previously-unknown to them, but they will only see either the name or the number - they won't know both. Returning the same value back to the server in a read/modify/write cycle should not modify that field, as the server should understand both forms.

Adding an output-only resource field

Fields in a resource entity that are only supplied by the server may be added. The server may validate that any client-supplied value in a request is valid, but it must not fail if the value is omitted.

Backwards-incompatible (breaking) changes

Removing or renaming a service, field, method or enum value

Fundamentally, if client code could refer to something, then removing or renaming it is a breaking change and must result in a major version increase. Code referring to the old name will cause failures at compile-time for some languages (such as C# and Java) and may cause execution-time failures or data loss in other languages. Wire format compatibility is irrelevant here.

Changing an HTTP binding

"Change" here is effectively "delete and add". For example, if you decide that you really want to support PATCH but your published version supports PUT, or you've used the wrong custom verb name, you may add the new binding, but you must not remove the old binding for all the same reasons as removing a service method is a breaking change.

Changing the type of a field

Even when the new type is wire-format compatible, this could change the generated code for client libraries and therefore must result in a major version increase. For compiled, statically-typed languages this can easily introduce compile-time errors.

Changing a resource name format

A resource must not change its name - which means collection names cannot be changed.

Unlike most breaking changes, this affects major versions as well: if a client can expect to use v2.0 access a resource that was created in v1.0 or vice versa, the same resource name should be used in both versions.

More subtly, the set of valid resource names should not change either, for the following reasons:

  • If it becomes more restrictive, a request that would previously have succeeded will now fail.
  • If it becomes less restrictive than previously documented, then clients making assumptions based on the previous documentation may be broken. Clients are very likely to store resource names elsewhere, in ways that may be sensitive to the set of permitted characters and the length of the name. Alternatively, clients may well be performing their own resource name validation to follow the documentation. (For example, Amazon gave customers a lot of warning and had a migration period when they started allowing longer EC2 resource IDs.)

Note that such a change may only be visible in the documentation of a proto. Therefore, when reviewing a CL for breakage, it is not sufficient to review non-comment changes.

Changing visible behavior of existing requests

Clients will often depend on API behavior and semantics, even when such behavior is not explicitly supported or documented. Therefore, in most cases changing the behavior or semantics of API data will be seen as breaking by consumers. If the behavior is not cryptographically hidden, you should assume that users have discovered it and will depend on it.

It is also a good idea to encrypt pagination tokens for this reason (even when the data is uninteresting), to prevent users from creating their own tokens and potentially being broken when the token behavior changes.

Changing the URL format in the HTTP definition

There are two kinds of change to consider here, beyond the resource name changes listed above:

  • Custom method names: Although not part of the resource name, a custom method name is part of the URL being posted to by REST clients. Changing a custom method name shouldn't break gRPC clients, but public APIs must assume they have REST clients.
  • Resource parameter names: A change from v1/shelves/{shelf}/books/{book} to v1/shelves/{shelf_id}/books/{book_id} does not affect the substituted resource name, but may affect code generation.

Adding a read/write field to a resource message

Clients will often perform read/modify/write operations. Most clients will not supply values for fields they are not aware of, and proto3 in particular does not support this. You could specify that any missing fields of message types (rather than primitive types) mean an update is not applied to those fields, but this makes it harder to explicitly remove such a field value from an entity. Primitive types (including string and bytes) simply cannot be handled this way, as there is no difference in proto3 between explicitly specifying an int32 field as 0 and not specifying it at all.

If all updates are performed using a field mask, this isn't a problem as the client won't implicitly overwrite fields it isn't aware of. However, that would be an unusual API decision: most APIs allow "whole resource" updates.