Syncing Concurrent Changes

See how ObjectBox Sync manages concurrent updates from multiple devices, and choose the approach that best matches your use case.

Offline-first apps do concurrent updates all the time. Often, these updates are independent of each other, and they can be applied in any order; once all devices (Sync clients) sent their individual updates to the Sync server, all data is synced without any issues.

But what happens when two devices edit the same object at the same time? This is a sync conflict, and ObjectBox Sync gives you several ways to handle it.

Default: Last Received Write Wins

By default, ObjectBox Sync resolves conflicts with a simple rule: the last write received by the Sync server wins. The server processes incoming updates in the order they arrive. If Device A and Device B both modify the same object while offline, whichever device syncs last will overwrite the other's changes.

spinner

This default is easy to reason about but has no notion of "wall-clock time." A device that was offline for a week and then syncs will overwrite a recent edit simply because its update arrived later.

circle-info

The default conflict resolution works at the object level: when a conflict is detected, the entire object is replaced.

ObjectBox Sync offers two property-level annotations to customize this behavior:

Annotation
Purpose

Sync Clock

A hybrid logical clock (HLC) that enables true "last write wins."

Sync Precedence

A developer-assigned priority value: "higher precedence wins."

You can use them independently or combine them — when both are present, precedence is compared first, and the sync clock acts as the tie-breaker for equal precedence values.

circle-info

Rule of thumb: use a sync clock to avoid unexpected overwrites. E.g., when a device was offline for a long time and then syncs again, its data is likely obsolete.

Sync Clock

A sync clock property is an unsigned 64-bit integer that ObjectBox Sync updates automatically on every put operation. It implements a so-called "Hybrid Logical Clock (HLC)" that combines:

  • the wall clock (physical time) of the device, and

  • a logical counter that increments to compensate for clock skew between devices.

This means two devices with different system clocks can still produce a consistent ordering of writes. On conflict, the write with the higher clock value wins — giving you true "last write wins" based on when the change was actually made, not when it was received.

circle-info

Initialize it to 0 in new objects; ObjectBox Sync takes care of the rest. The sync clock uses a custom internal format — do not read, interpret, or set clock values yourself.

circle-exclamation

How It Works

spinner

Even though Device A synced after Device B, the server keeps Device B's change because its clock value is higher. Without a sync clock, Device A's later-arriving write would have silently won.

Defining a Sync Clock Property

Add a 64-bit integer property (unsigned if the language allows) to your synced entity and annotate it as a sync clock. Initialize it to 0 for new objects. There can be only one sync clock property per entity type.

About the Hybrid Logical Clock (HLC)

A pure wall clock is unreliable in distributed systems — device clocks drift, are set manually, or jump after NTP corrections. A pure logical clock (like a Lamport clock) gives you ordering but no connection to real time.

The HLC gives you the best of both:

  • It tracks real time as closely as possible.

  • It never goes backwards, even if the wall clock is adjusted.

  • It uses a logical counter to break ties when two events share the same millisecond timestamp.

This makes the sync clock safe to use for conflict resolution without requiring perfectly synchronized clocks across devices.

circle-info

ObjectBox Sync uses a special HLC implementation with additional compensation for clocks set in the future. This makes the ObjectBox Sync clock more robust in situations with concurrent offline edits.

Sync Precedence

While the sync clock answers "which write happened last?", sync precedence answers "which write is more important?" It is an unsigned 64-bit integer property whose value you control. On conflict, the write with the higher precedence value wins.

This is a powerful mechanism for encoding business logic directly into conflict resolution.

circle-exclamation

Defining a Sync Precedence Property

Like the sync clock, there can be only one sync precedence property per entity type.

circle-exclamation

Conflict Resolution Order

When both a sync precedence and a sync clock are defined on the same entity type, conflicts are resolved as follows:

spinner

Example: Closing an Order

Consider an order-management app where orders can be edited until they are closed. Once closed, no further edits should overwrite the closed state — even if a device with an older, still-open version syncs later.

Approach: when closing an order, set the precedence property to a high value (e.g. 1000). Regular edits keep the default precedence of 0.

spinner

Even though Device A's write arrived later, it cannot overwrite the closed order because its precedence (0) is lower than the closing write (1000).

circle-info

Once Device A receives the closed version (with precedence 1000), subsequent updates from Device A will sync again — because they now carry the same or higher precedence. The sync clock then serves as the tie-breaker.

More Use Cases for Sync Precedence

Linear workflow states. Model a pipeline where objects move through stages (e.g. Draft → Review → Approved → Published). Assign increasing precedence values to each stage (e.g. 100, 200, 300, 400). A later stage always wins over an earlier one, preventing accidental rollbacks.

circle-info

It's generally a good idea to use larger increments like 100 or 1000 for precedence values. This allows for future extensions to the workflow and introducing values in-between.

Role-based authority. Give admin or manager edits a higher precedence than regular-user edits. For example, a manager sets precedence to 1000 when overriding a field; normal users leave it at 0. This ensures managerial corrections are never silently overwritten by regular edits.

Checkpoint timestamps. Periodically "approve" or "checkpoint" an object by updating its precedence to the current timestamp (e.g. Unix seconds). Any changes made before the checkpoint that arrive late are discarded because their precedence is lower. Only changes made after the checkpoint — which naturally carry a higher precedence — will be accepted.

circle-exclamation

Summary

Using a Sync clock is a solid default choice for most apps.

Mechanism
Conflict rule
Controlled by
Best for

Default

Last received write wins

Arrival order

Simple apps, non-critical overwrites

Sync Clock (HLC)

Last actual write wins

Automatic (ObjectBox)

Fair ordering despite offline periods

Sync Precedence

Higher precedence wins

Developer

Business logic (states, roles, locks)

Precedence + Clock

Precedence first, then HLC

Both

Full control with a sensible fallback

Last updated