Skip to main content
Version: 0.17

Schema Evolution

Schema evolution lets different versions of your app exchange messages safely — a v2 writer can produce a message that a v1 reader can still decode, and vice versa.

Two Modes

Enable this when services may run different versions at the same time — for example, during a rolling deployment or when clients are not updated immediately.

final fory = Fory(compatible: true);

In compatible mode, Fory includes enough field metadata in each message so that the reader can skip unknown fields and use defaults for missing ones. Use stable field IDs (see below) to anchor the schema across changes.

Schema-Consistent Mode (default)

Both sides must have the same model. Fory validates that the schemas match and will reject messages from a different schema version. Use this when all services are always updated together and you want schema mismatches to be caught as fast errors.

final fory = Fory(); // compatible: false by default

Setting Up for Evolution

To use compatible mode safely, mark your structs with @ForyStruct(evolving: true) (the default) and assign a stable @ForyField(id: ...) to every field before you ship your first payload:

@ForyStruct(evolving: true)
class UserProfile {
UserProfile();

@ForyField(id: 1)
String name = '';

@ForyField(id: 2, nullable: true)
String? nickname;
}

If you add field IDs after payloads are already in production, existing stored messages won't have them and evolution won't work correctly.

What You Can Safely Change

Safe changes (compatible on both sides):

  • Add a new optional field with a new, unused field ID.
  • Rename a field — as long as the @ForyField(id: ...) stays the same.
  • Remove a field — the peer will just ignore the missing value and use the Dart default.

Unsafe changes (may break existing messages):

  • Reuse an existing field ID for a different field.
  • Change a field's type to an incompatible type (e.g., Int32String).
  • Change the registration identity (id, namespace, or typeName) of a type after messages are in production.
  • Change a field's logical meaning without changing its ID.

Cross-Language Notes

Evolution only works when all runtimes that exchange messages agree on:

  1. The same compatible setting.
  2. The same type registration identity (numeric ID or namespace + typeName).
  3. The logical meaning of field IDs.

Test rolling-upgrade scenarios with real round trips before deploying.