V5 - Java Server DocumentationThese docs are for Java Server V5 Only. For all other languages and platforms, continue using SDK v4 until V5 support is available.
You can react to changes in your local store by setting up a store observer.
You can read more about store observers in Accessing Data.
Store observers are useful when you want to monitor changes from your local Ditto store and react to them immediately. For instance, when your end user updates their profile, asynchronously display changes in realtime.
Understanding Observer Backpressure
When using store observers, callbacks fire every time matching data changes in your local store. In high-frequency scenarios—such as IoT sensors publishing multiple times per second or real-time position tracking—callbacks can fire faster than your application can process them. This leads to a backlog of pending callbacks, excessive memory consumption, and potential crashes.
To handle this, Ditto provides registerObserver with DittoChangeListenerWithNextSignal, which gives you explicit control over when the observer can deliver the next update. Instead of automatic callback delivery, you call a signalNext function when your application is ready for the next batch of changes.
Without backpressure control, observer callbacks in high-frequency scenarios can overwhelm your application. Always use registerObserver with DittoChangeListenerWithNextSignal when updates may arrive faster than your app can process them.To determine if you need back pressure control see Choosing the Right Observer Method below.
Setting Up Store Observers
Store Observer with Backpressure Control (Recommended)
Use registerObserver with DittoChangeListenerWithNextSignal to control when your observer receives updates. The callback includes a signalNext function that you call when ready for the next update.
import com.ditto.java.DittoStoreObserver;
import static com.ditto.java.serialization.DittoCborSerializable.buildDictionary;
// DittoQueryResult passed to your lambda expression will be auto-closed after
// the lambda is invoked. You MUST NOT save the result or it's associated
// DittoQueryResultItem objects as they will be invalid outside the lambda's scope.
DittoStoreObserver observer = ditto.getStore().registerObserverWithSignalNext(
"SELECT * FROM cars WHERE color = :color",
buildDictionary().put("color", "blue").build(),
(result, signalNext) -> {
try (result) {
// Process the current result
updateUI(result.getItems());
// Signal readiness for next update
signalNext.signal();
}
}
);
For UI updates, consider calling signalNext() after your render cycle
completes—for example, inside requestAnimationFrame in JavaScript or after
DispatchQueue.main.async completes in Swift.
Basic Store Observer
For low-frequency updates where backpressure is not a concern, use the simpler registerObserver method.
This approach does not provide backpressure control. If updates arrive faster
than your callback can process them, callbacks will queue up and may cause
memory issues. For high-frequency scenarios, use registerObserver with
DittoChangeListenerWithNextSignal instead.
To associate arguments with your query, add them as a parameter.
import com.ditto.java.DittoStoreObserver;
import static com.ditto.java.serialization.DittoCborSerializable.buildDictionary;
// DittoQueryResult passed to your lambda expression will be auto-closed after
// the lambda is invoked. You MUST NOT save the result or it's associated
// DittoQueryResultItem objects as they will be invalid outside the lambda's scope.
DittoStoreObserver observer = ditto.getStore().registerObserver(
"SELECT * FROM cars WHERE color = :color",
buildDictionary().put("color", "blue").build(),
(result) -> {
// handle change
}
);
Critical Memory Management: QueryResult and QueryResultItem objects should be treated like database cursors. Always extract the data you need immediately and then close/dematerialize them. Never store QueryResult or QueryResultItem instances directly between observer emissions as this will cause memory bloat and potential crashes.
Choosing the Right Observer Method
| Scenario | Recommended Method |
|---|
| High-frequency updates (sensors, real-time tracking) | registerObserver with DittoChangeListenerWithNextSignal |
| UI updates that need render-cycle synchronization | registerObserver with DittoChangeListenerWithNextSignal |
| Async processing that may take time | registerObserver with DittoChangeListenerWithNextSignal |
| Low-frequency updates (user profile changes) | registerObserver with DittoChangeListener |
| Simple use cases with infrequent changes | registerObserver with DittoChangeListener |
For more information on memory management with observers, see the Diffing
Results section which covers
QueryResult lifecycle and proper cleanup patterns.
Canceling a Store Observer
To cancel a store observer, call close() on the observer object.
Once canceled, the store observer will stop processing in the background and will no longer call the provided callback.
Accessing Store Observers
To access store observers from the local Ditto store:
ditto.getStore().getObservers();