_SDK 5.1 common capabilities β shared by all SDK platforms. Shown in the overall SDK view; platform filters show that platformβs own blocks only.
This makes normalized data models practical at the edge. Product catalogs, order histories, task assignments, and multi-tenant views can stay separated into logical collections without forcing every screen to coordinate multiple reads.Joins use data already present in the local store. They do not fetch missing data from peers, and they are not supported in sync-subscription queries. The inner collection normally requires an appropriate index; use It can improve queries such as:Field order matters. Put fields used in equality filters first, followed by fields used for range filters or sorting. Composite indexes can also include array and object values. If you are unsure which fields to index, run Ditto identifies the range predicate and recommends an index on Run the suggested statement yourself, or use
Ditto SDK 5.1 β Faster Local Queries. More Capable Query Engine.
The new Ditto Edge SDK 5.1 delivers major features and optimizations across five areas. The release includes 77 platform and 112 SDK-specific improvements.Key takeaway: Existing applications get better performance and more efficient memory usage on the same hardware simply by upgrading to SDK 5.1.1. Performance: Faster Queries, Lower Memory Use- Evictions are 53.6Γ faster and deletes are 43.0Γ faster on average. This speeds up routine local data clean-up, like clearing the orders from a point-of-sale terminal.
- A full-collection
COUNT(*)fell from 149.03 ms to 0.89 ms, which is 167Γ faster on average. This speeds up badge counts and dashboard totals across an app.
JOINcombines local collections in a singleSELECT, removing the need to run separate queries and merge their results in application code.ADVISElooks at a query and tells you the indexes to create to make it faster, all without running it or reading a document.
- If a device is lost or stolen, itβs easier to revoke the deviceβs certificate, disconnect it from the mesh, and prevent the device from sending or receiving data.
- Revocations travel peer to peer and reach devices even when they canβt see the cloud. Connections from revoked peers are terminated and refused.
- Identifying problems is easier with better tools for analyzing deployed devices, starting with support bundles that capture the deviceβs effective configuration.
- Corrupted replication metadata is detected and rebuilt automatically, so field issues can be solved without a site visit.
- Multicast allows Ditto to scale to large networks with many edge devices. Edge devices subscribe to a shared group for data sync, which has lower connection cost at scale.
- Opt-in beta capability in the Swift, Kotlin/KMP, Flutter, and Rust SDKs. Support will expand to additional SDKs in the future.
- Upgrading to Ditto SDK 5.1 changes the on-disk index format.
- If you need to downgrade from Ditto SDK 5.1 or later, migrate through Ditto 5.0.2+. You can also migrate through Ditto 4.14.6+.
1. Performance: Faster Queries, Lower Memory Use
Edge applications feel database performance differently from cloud applications. A slow local query doesnβt just delay a response from a server. It can block a screen transition, increase battery use, create UI churn, or consume memory on a device with limited resources.SDK 5.1 delivers a major leap in local query performance. Applications can load data faster, complete writes sooner, and react to changes more quicklyβeven as their local datasets and workloads grow.The improvements span nearly every kind of local data operation: selects, indexed reads, inserts, updates, deletes, evictions, aggregations, and observers. In practice, this means more responsive user experiences, less time waiting for data operations, and greater capacity on the same device hardware.These gains come from improvements throughout the local data path. Ditto performs less decoding and allocation, finds documents more directly, executes mutations more efficiently, avoids unnecessary observer work, and reduces full database scans during subscription changes. An opt-in relaxed durability mode can further reduce disk synchronization for rebuildable sync metadata without changing the durability of application documents.Android retail benchmark
The gains are broad rather than limited to one optimized query path. In testing on an Orion O6 Android device, SDK 5.1 was faster in 70 of 71 measured scenarios, with one result too close to call. The 72-scenario suite modeled an offline-first retail application with approximately 93,000 documents across seven synced collections.These results compare median runtimes from Ditto 5.0.3 and Ditto 5.1.0. Performance varies by device, data, indexes, and query mix, so test representative workloads on your target hardware.
Dramatically faster document counts
A count is a query that answers one simple question: how many documents match? Apps use counts all the time to show how many orders are open, how many items are in stock, or how many results a search found.One of the largest individual improvements is full-collection document counting. Ditto 5.1.0 adds an optimized path forCOUNT(*), reducing the benchmarkβs median execution time from 149.03 ms to 0.89 msβapproximately 167Γ faster. Counts with a filter also improved by approximately 4.4Γ.These improvements accelerate queries such as
SELECT COUNT(*) FROM tasks and SELECT COUNT(*) FROM tasks WHERE status = 'open', making it substantially faster to calculate totals for dashboards, backlog checks, pagination, and application status displays.Lower memory use
Edge devices like phones, tablets, and point-of-sale terminals have a fixed amount of memory that every app shares. The less memory Ditto uses, the more room your app has for its own work, and the less likely the operating system is to slow it down.In a separate Android workload that grew a collection from 3,000 to 30,000 documents, Ditto 5.1.0 delivered approximately 2.1Γ as many observer results while using less memory than Ditto 5.0.3.Total PSS estimates the processβs physical RAM footprint, and peak Total PSS is the highest that footprint reached during the test. Native heap covers Dittoβs Rust coreβthe pool of memory the program sets aside while it runs to hold its working data. Tombstone cleanup, bulk mutations, and retained disconnected sync sessions are also bounded more carefully to reduce peak memory in high-volume deployments.
2. Query Engine: New Ditto Query Capabilities
Performance is only half of the query story in this new release. DQL (Ditto Query Language) gains the two capabilities customers asked for the most:JOIN and composite indexes. Customers can also run ADVISE to identify any indexes that would speed up query performance.Join collections locally
A join is a query that combines related data from two collections into one result. It matches records that share a value, like a task and the project it belongs to, so your application gets one combined answer instead of two separate lists.SELECT statements on edge devices can now join multiple local collections. This removes the need to coordinate separate queries and merge their results in application code.DQL
ADVISE when you need an index recommendation.Create composite indexes
An index is a lookup structure the database maintains so it can find matching documents without reading the whole collection, much like the index at the back of a book. A composite index covers two or more fields at once, so a query that filters on one field and sorts by another can be answered in a single lookup.Edge devices now support composite indexes over multiple fields. A single composite index can accelerate queries that repeatedly filter or sort by the same combination of fieldsβfor example, tenant and time, status and assignee, or location and category.The following index is designed for queries that filter tasks bystatus and sort them by createdAt:DQL
DQL
ADVISE against the query to get an index recommendation.Find the right indexes with ADVISE
ADVISE turns index optimization into a guided workflow. Prefix a query with ADVISE, and Ditto plans how it would execute that query without actually running it or reading any documents. It inspects the queryβs filters, sorts, projections, and joins. Then, Ditto spots the places where the engine would have to scan the whole collection. For each one, the response explains why an index would help and provides a ready-to-run CREATE INDEX statement.For example, advise a query that filters tasks by estimated effort:DQL
estimateHours:ADVISE AND PROVISION to create the recommended indexes automatically. ADVISE can also recommend composite and covering indexes for more complex filters, sorting, projections, and joins. It is currently available on edge devices.Return changed documents with RETURNING
RETURNING lets an INSERT, UPDATE, DELETE, EVICT, or TOMBSTONE statement return data from the documents it changed. A data mutation and its confirmation can become one operation. Applications can receive the affected data immediately instead of issuing a second query.For example, update matching documents and return their IDs and new values in one operation:DQL
RETURNING is especially valuable with deletes and evictions because it can return document contents before they are removed. It also supports projections, expressions, aliases, and aggregates such as RETURNING COUNT(*) AS removed.Ditto 5.1 also adds INSERT ... SELECT for creating documents directly from query results.Control long-running requests
A single expensive query on an edge device can hold resources that are needed by the rest of an application. Two new system parameters can catch runaway queries before they cause performance issues.Set either parameter to
0 to disable it. Request history can also filter by request type or explicit profiling requests.3. Security: Expanded Certificate Revocation
Security policies at the edge need to keep working even when devices are not continuously connected to the cloud. Security teams lose sleep over what happens when an edge device is lost or stolen.SDK 5.1 will help security teams sleep better. Certificate revocation information now propagates securely from Ditto Server to edge devices and from peer to peer throughout the mesh. As peers connect, they share the latest revocation information.Revocation enforcement is enabled by default. Peers reject new connections that present a revoked certificate and terminate matching active connections when a revocation arrives. This isolates revoked clients and prevents them from reconnecting through another peer in the mesh.Every hop verifies the revocationβs signature against its trusted certificate authority keys, the source that issues each deviceβs identity credentials. A compromised peer cannot forge revocations.4. Troubleshooting: Identify Problems and Recover Automatically
A device misbehaving in the field is a hard problem to tackle in edge computing. Itβs expensive and inefficient to fly an engineer out to attach a debugger to a tablet that is 3,000 miles away in the back of a restaurant.SDK 5.1 adds new options to remotely collect edge device data and identify the root cause of an issue. Support bundles now includeconfig_snapshot.json, which records the effective DittoConfig, transport configuration, system parameters, and SDK version at capture time. This provides essential device config information at the start of every investigation or support case.Other new remote diagnostics include:- A configurable Unix
debug_socketlets you remotely run DQL diagnostics against an edge device.- You can inspect a live device directly rather than reproducing the problem in a lab.
- Nine new network counters under
ditto.network.dsoq.*surface Ditto Sync over QUIC (DSOQ) protocol failures in your existing production metrics, without debug logs. - SQLite metrics distinguish the application data store from replication metadata databases on supported Unix platforms, showing which one is driving disk activity.
5. Transport at Scale: Multicast Beta
Today, Dittoβs default peer-to-peer model establishes a session between every pair of edge devices. The total connection count grows with the square of the mesh size, O(NΒ²). Six devices need 15 connections, and sixty devices need 1,770. Connection maintenance eventually becomes the dominant cost in environments with a large number of edge devices like a mall, a ship, a concert venue, or an aircraft.Multicast is now available in SDK 5.1 as an opt-in beta. Devices join a shared multicast group rather than pairing off, which drops the connection count from O(NΒ²) to O(N). There is one group membership per device.A sender would typically transmit an update once per device in the mesh, which is O(N). Multicast enables the sender to publish a single broadcast to the group, which is approximately O(1). There is one send, no matter how many devices are listening.The transport is built on reliable multicast (NORM, RFC 5740) combined with Dittoβs data reconciliation, so peers recover missed data and catch up after joining or reconnecting.When multicast is configured and available, it becomes the preferred replication path. Traffic is encrypted for the group, and existing peer-to-peer transports remain active as automatic fallback for peers the group cannot reach. Documents and attachments both replicate over multicast, including repair of missing attachment data.Upgrading to 5.1
The upgrade to Ditto Edge SDK 5.1 is seamless. Simply bump your dependency to 5.1.0 and Ditto handles the rest. An index migration will run automatically the first time your app starts. Data sync is backward-compatible, and 5.1 peers will work with 5.0 and v4 peers while you roll out gradually.Tested rollback compatibility
Ditto Edge SDK 5.1 has undergone extensive backward-compatibility and rollback testing to ensure production deployments can safely return to supported earlier SDK versions when needed.The 5.1 SDK changes the on-disk index format. If you need to downgrade from Ditto 5.1 or later, migrate through Ditto 5.0.2+. You can also migrate through Ditto 4.14.6+. These versions recognize the updated index format and automatically revert it to the format understood by earlier versions.During the downgrade, composite indexes are replaced with single-field indexes, one for each of the composite indexβs keys.See index migration and downgrade behavior for details. As with any production upgrade, validate the procedure with representative application data before deployment.Kotlin-Specific Changes
Data Streams Public Preview
Android Kotlin applications can now open low-latency, bidirectional byte streams to specific reachable peers on named topics. Reliable streams deliver messages in order or fail the connection. Unreliable streams favor latency and may lose, reorder, or duplicate messages.Stream payloads are ephemeral: they are never stored, queried, or synchronized as documents. The preview requires explicit opt-in and NGN configuration. See Data Streams for setup, lifecycle, framing, reconnection, and backpressure guidance.Observability and lifecycle improvements
- Kotlin and Kotlin Multiplatform applications can opt into OpenTelemetry spans for DQL, transaction, and attachment operations.
- Slow Flow collectors now receive a single coalesced, up-to-date result instead of a backlog of stale observer results.
- DQL argument maps accept additional Kotlin scalar, collection, attachment, and serializable values.
- Memory and lifecycle fixes cover observers, diffs, attachments, query results, subscriptions, presence, and authentication handlers.
Kotlin Specific Changelog
Added:- Data Streams Public Preview: low-latency, bidirectional byte streams between reachable Android peers. See Data Streams for requirements and usage guidance.
transports_wifi_aware_instant_communication_enabledsystem parameter (default:true). Set it tofalseto disable Wi-Fi Aware Instant Communication Mode on single-radio devices that experience Wi-Fi instability from multi-channel switching. (#21321)transports_ble_warm_gatt_cache_enabledsystem parameter (Android, default:false). When enabled before sync starts, a cleanly disconnected peripheralβs GATT connection is kept warm so reconnecting to a known peer skips BLE service discovery. (#NETW-1976)- Opt-in OpenTelemetry tracing for DQL queries, transactions, and attachment operations, configured through the new
DittoInstrumentation.configure()API. (#SDKS-3219) Enum,Char,UByte,Short,Byte, andUIntvalues can now be individually converted toDittoCborSerializableorDittoJsonSerializable. (#SDKS-3823)DittoTransportConfig.PeerToPeer.MulticastBetaandDittoTransportConfig.PeerToPeer.multicastBetafor configuring the beta reliable UDP multicast transport. The transport is disabled by default, with availability limited to supported platforms during the beta. On Android and iOS, changes requested while sync is active take effect after sync is stopped and successfully started again, when Ditto validates platform prerequisites. (#SDKS-4471)DittoConnectionType.Multicastenum option representing beta reliable UDP multicast connections. Transport availability is limited to supported platforms during the beta. (#SDKS-4471)
DittoStore.observe()andDittoStore.observeWithDiff()now coalesce writes that arrive while a slow collector is busy, delivering a single up-to-date emission rather than a backlog of stale results. (#SDKS-3836)
- The
DittoPresence.connectionRequestHandlersetter no longer leaks the previous handler and its captured user closure on every reassignment. (#22500) - Memory leak in
DittoStore.fetchAttachmentandDittoStore.newAttachmenton repeated calls for the same attachment. (#SDKS-1898) - Memory leak in
DittoStoreObserverwhen the suppliedonClosecallback throws. (#SDKS-1898) - Memory leaks in
DittoStore.observe,DittoStore.collect,DittoStore.registerObserver, andDittoStore.fetchAttachmentwhen a callback races with a closingDittoinstance. (#SDKS-1898) - Memory leak on each access to
DittoConfig.default,DittoTransaction.info,DittoSync.subscriptions,DittoSyncSubscription.queryArgumentsCborData, andDittoSyncSubscription.queryArgumentsJsonString. (#SDKS-1898) - Memory leak in the
DittoDiff-receiving overloads ofDittoStore.observe,DittoStore.collect, andDittoStore.registerObserver. (#SDKS-4018) DittoAuthenticatornow releases itsexpirationHandlerbinding promptly when the parentDittoinstance is closed. (#SPO-651)
- Wi-Fi Aware TCP sync on Android no longer breaks when the shared TCP listener is bound to IPv4 or disabled. (#22612)
- Android BLE central connections with 2M PHY enabled no longer start GATT service discovery while PHY negotiation is still in flight, which could silently leave the connection unusable on devices that are slow to negotiate PHY. (#NETW-1452)
- Wi-Fi Aware peer-to-peer connections between Android 13+ devices no longer fail when each side independently selects a different security configuration even though a mutually supported one is available. (#NETW-900)
NullPointerExceptioninStableBluetoothPlatform.onCharacteristicReadandonCharacteristicChangedon Android 12 and below whenBluetoothGattCharacteristic.getValue()returns null. (#SPO-862)
- The DQL query
argumentsparameter isMap<String, Any?>?(nullable) again across the Kotlin SDK, matching v4. Passnullto indicate no arguments. (#SDKS-3820) Enum,Char,Short,Byte,UByte,UInt,Array<*>, andSet<*>values can now be used as DQL query arguments. Previously these types caused anIllegalArgumentExceptionat runtime. (#SDKS-3823)- APIs using
Map<String, Any?>?arguments now correctly acceptDittoAttachmentandDittoCborSerializablevalues inside the map, matching the documented attachment-insert pattern. (#SDKS-4195)
DittoStoreObserverrace condition where the observer would never close when the event handler threw an exception, caused by the observer reference not being set before the FFI callback fired. (#21923)- Android apps with
isMinifyEnabled = trueno longer crash on Ditto initialization. The published Kotlin AARs now bundle the requiredconsumer-rules.prokeep rules. (#SDKS-2626) Ditto.sync().stop()no longer crashes the app withForegroundServiceDidNotStartInTimeExceptionon Android 14+ when the foreground service is enabled. (#SPO-941)
DittoPeerOs.Tvos. The entry is retained so peers running older SDK versions on tvOS can still be identified in the presence graph. (#SDKS-3944)
Added: a new system parameter transports_websocket_watchdog_interval_secs to adjust WebSocket client watchdog. (#21885) Fixed: Corrected a formalisation bug causing DQL statement filters to never match, that was affecting observers using projections. (#QE-1056) Fixed: Corrected a hang when accessing
system:data_sync_info. (#QE-1095)5.0.3 Kotlin Specific Changes
Fixed: Memory leak inDittoStore.fetchAttachment and DittoStore.newAttachment on repeated calls for the same attachment. (#SDKS-1898) Fixed: Memory leak in DittoStoreObserver when the supplied onClose callback would throw. (#SDKS-1898) Fixed: Memory leaks in DittoStore.observe, DittoStore.collect, DittoStore.registerObserver, and DittoStore.fetchAttachment when a callback would race with a closing Ditto instance. (#SDKS-1898) Fixed: Memory leak on each access to DittoConfig.default, DittoTransaction.info, DittoSync.subscriptions, DittoSyncSubscription.queryArgumentsCborData, and DittoSyncSubscription.queryArgumentsJsonString. (#SDKS-1898) Fixed: APIs using Map<String, Any?>? arguments now correctly accept DittoAttachment and DittoCborSerializable values inside the map, matching the documented attachment-insert pattern. (#SDKS-4195) Added: a new timeout for WebSocket connect that is adjustable by system parameter transport_websocket_connect_timeout. (#21866) Changed: Mesh chooser now enforces per-transport connection limits instead of a shared WiFi radio budget, preventing one transport (e.g. TCP) from starving others (e.g. AWDL, WiFi Aware). When a transport is at capacity, the behavior for new inbound connections is configurable: reject, drop oldest, drop newest, or accept. Peers at capacity reject inbound connections with a new
ConnectError::AtCapacity variant and back off exponentially to avoid retry storms. (#NETW-1586) Added: Automatic downgrading of the SP store from V3 to V2 on start-up. (#QE-595) Added: Automatic downgrading of the SQLite3 schema from V3 to V2 allowing for downgrading from version 5.1. (#QE-595) Added: disable_replication_gc_on_evict system parameter (default false). When set to true, calls to evict() no longer trigger immediate per-peer metadata cleanup; the periodic background replication GC continues to reclaim metadata for disconnected peers once they exceed the TTL (~7 days by default). Intended as an opt-in escape hatch for deployments where eviction-time filesystem work contributes to write-path latency. (#QE-686) Added: Optional background task to reclaim unused space in the Small Peer store and record space usage metrics. (#QE-779) Changed: The way query profile timing and count data is recorded to reduce overheads. (#QE-811) Fixed: A bug in internal subscription bookkeeping caused long-connected Document Sync sessions to become disabled due to spurious capacity errors. (#SPO-1011) Added: The option to set the DITTO_SQLITE3_MAX_CONNECTIONS parameter lower than 32, down until 16 (#SPO-668) Changed: The default value of SQLITE3_MAX_CONNECTIONS parameter to 32, down from 60 (#SPO-668). Changed: The replication_session_request_timeout_secs and blob_session_request_timeout_secs system parameters are no longer functional; the timeouts have been removed. They are accepted for backwards compatibility but have no effect. (#SPO-869)5.0.2 Kotlin Specific Changes
Fixed: Memory leak on theDittoDiff-receiving overloads of DittoStore.observe, DittoStore.collect, and DittoStore.registerObserver. (#SDKS-4018) Changed: DittoStore.observe() and DittoStore.observeWithDiff() now coalesce writes that arrive while a slow collector is busy, delivering a single up-to-date emission rather than a backlog of stale results. (#SDKS-3836) Removed: Spurious
dsoq.cbor CBOR warning log during auth client initialization. (#21646) Added: Garbage collection for document sync sessions now imposes limits on the number of disconnected sessions that will be retained, even if the TTL is not exceeded. (#DS-1065) Fixed: the query engines erroneously build index spans not including the higher end for the BETWEEN operator. Fixed to include it. (#QE-896)5.0.1 Kotlin Specific Changes
Fixed:DittoStoreObserver race condition where the observer would never close when the event handler threw an exception, due to the observer reference not being set before the FFI callback fired. (#21923) Fixed: Android apps with isMinifyEnabled = true no longer crash on Ditto initialization; the published Kotlin AARs now bundle the required consumer-rules.pro keep rules. (#SDKS-2626) Fixed: DQL query arguments parameter is Map<String, Any?>? (nullable) again across the Kotlin SDK, matching v4. Pass null to indicate no arguments. (#SDKS-3820) Fixed: Enum, Char, Short, Byte, UByte, UInt, Array<*>, and Set<*> types can now be used as DQL query argument values; previously these types caused an IllegalArgumentException at runtime. (#SDKS-3823) Added: Enum, Char, UByte, Short, Byte, and UInt values can now be individually converted to DittoCborSerializable or DittoJsonSerializable. (#SDKS-3823) Fixed: DittoAuthenticator now releases its expirationHandler binding promptly when the parent Ditto instance is closed. (#SPO-651) Fixed: NullPointerException in StableBluetoothPlatform.onCharacteristicRead and onCharacteristicChanged on Android 12 and below when BluetoothGattCharacteristic.getValue() returns null. (#SPO-862)_SDK 5.0 introduced DQL for all data operations and major core, performance, and upgrade changes. Shared by all SDK platforms; platform filters show that platformβs own blocks.Each SDK provides access to the config object with language-appropriate naming conventions. See the SDK-specific migration guides for exact syntax.This maintains the same data modeling semantics youβre currently using. Once your application is stable on v5, follow the strict mode migration guide and work with the Ditto CX team to migrate to schema-free data modeling and take advantage of the improved developer experience.Example:This is equivalent to Object Transformation Syntax:Key behaviors:These collections provide unprecedented visibility into Dittoβs internal state, making it easier to monitor, debug, and optimize your applications.
DQL for All Data Operations
DQL for All Data Operations
Ditto 5.0 completes the transition to DQL (Ditto Query Language) as the single API for all data operations. The legacy query builder has been removed.What Changed
- Legacy query builder removed: All
store.collection()methods and fluent query APIs are no longer available - Full feature parity: Every legacy operation has a DQL equivalent
- Single query language: DQL handles reads, writes, subscriptions, and observers
- Works everywhere: Same syntax across mobile, server, and web
- SQL-familiar: Standard SQL patterns for immediate productivity
Why One Query Language
A unified query language means one implementation to maintain, faster feature delivery, and consistent behavior across all platforms. New capabilities and optimizations benefit every SDK simultaneously.Core Improvements
Simplified Initialization & Configuration
Ditto 5.0 introduces a completely redesigned initialization flow built around the newDittoConfig pattern. This replaces the previous Identity-based approach with a clearer, more predictable setup that aligns with modern best practices.Whatβs New
The new configuration system provides:- Unified configuration object: All initialization parameters are set through
DittoConfigfactory methods - Fallible initialization: Explicit error handling during setup catches configuration issues early
- Asynchronous patterns: Native async/await support where appropriate for each platform
- Simplified authentication: Clearer authorization client thatβs easier to understand and implement
What This Solves
The previous initialization flow required multiple unintuitive settings due to legacy compatibility concerns. For example, developers had to setdisableCloudSync = true to connect to a Big Peer, which was confusing and non-obvious.The new pattern consolidates all configuration into a single, coherent flow that makes the relationship between settings explicit and easier to reason about.Accessing Configuration at Runtime
After initializing Ditto, you can access the configuration object to retrieve settings like the database ID. This replaces methods likegetAppID() from v4:Availability
TheDittoConfig pattern was introduced as an option in v4.12 and becomes the only initialization method in v5.0.Migration guides for each SDK are available in the v5 documentation. If youβre currently on v4.11 or later, the migration path is straightforward.
Schema-Free Data Modeling
Ditto 5.0 transforms the developer experience by making DQL strict mode disabled by default. This eliminates the need to define collection schemas or CRDT types upfront, allowing you to insert nested objects and data structures without pre-defining types.Whatβs New
With strict mode disabled by default, Ditto changes how objects are stored and synchronized:Objects default to MAP type instead of REGISTER typeThis fundamental change means:- Field-level sync: Ditto syncs individual field changes instead of replacing entire objects
- Automatic type inference: No need to pre-define collection schemas or CRDT types
- Nested structures: Insert complex JSON-like documents without type definitions
- Concurrent updates merge: When peers update different fields simultaneously, both changes are preserved
What This Solves
This change provides a more simplified data management structure with two key benefits:- Add-wins behavior on objects: When peers create or modify objects, additions are preserved rather than overwritten
- Field-level delta sync on all nodes: Every field in your document syncs independently, reducing bandwidth and enabling fine-grained conflict resolution throughout the entire document structure
New Customers
Schema-free data modeling is enabled by default in Ditto 5.0. No action is neededβsimply start building with automatic type inference and field-level sync.Migrating Customers
Migration Considerations
Strict mode is a local configuration setting on each device that controls how DQL interprets and writes data structures.How it works across peers:- Data syncs successfully between peers regardless of different strict mode settings
- Each peer interprets data based on its own strict mode setting when reading/writing
- With
DQL_STRICT_MODE=false: Objects are inferred as MAPs (field-level merging) - With
DQL_STRICT_MODE=true: Objects without explicit type definitions are treated as REGISTERs (whole object replacement)
- Collections/documents created, modified, or read while strict mode is disabled will use inferred types (objects β MAPs by default)
- Collections/documents created, modified, or read while strict mode is enabled use default REGISTER type and require explicit definitions for other types
- If mixing settings, explicitly define MAP types in collection definitions on peers with strict mode enabled
Learn more about strict mode, cross-peer synchronization, and troubleshooting in the DQL Strict Mode documentation.
New DQL Query Features
Ditto 5.0 introduces several new DQL syntax features that expand query capabilities and make complex queries easier to write.CASE Statements
Add conditional logic to queries with CASE expressions:BETWEEN Expressions
TheBETWEEN operator provides a shorthand for defining an inclusive numeric range for an expression result.Syntax:(a >= 1 AND a <= 10).Array & Object Search Syntax
Test elements within arrays or objects usingANY, EVERY, or ANY AND EVERY operators:Syntax:- IN - Searches the array/object directly
- WITHIN - Searches recursively through nested structures
- ANY - Returns true if at least one element matches
- EVERY - Returns true if all elements match (empty arrays/objects pass)
- ANY AND EVERY - Like EVERY, but empty arrays/objects fail
Array & Object Transformation Syntax
Create new arrays and objects by transforming existing data structures:Array Transformation Syntax:- If
sourceevaluates toMISSING, the result isMISSING - For arrays: if
sourceis not an array/object, the result isNULL - For objects: duplicate field names will overwrite previous values
- Elements/fields where
valueExprevaluates toMISSINGare excluded from the result
Extended String Literals
Support for escape sequences in strings:Hexadecimal Numeric Constants
Additional numeric literal formats:Performance & Platform
DQL Query Performance Improvements
Your applications will feel noticeably faster and more responsive. Data queries complete faster, delivering a snappier user experience whether users are searching, filtering, or loading content. These performance gains happen automaticallyβno code changes required.Query Planner Enhancements
The DQL query planner has been enhanced to recognize more optimization opportunities:- Automatic ID scan conversion: Equality filters on
_idfields automatically convert to ID scans, bypassing index lookups when exact document IDs are known - Deferred document fetching: Query planner can defer fetching full documents until after sorting and applying offset/limit when index access supports it
- Improved covering index support: More scenarios where the query planner can satisfy queries entirely from index data without retrieving documents
- Index-only queries: Additional cases where queries can be answered using only index scans
Streaming Query Execution
The query engine now uses streaming interfaces internally:- Reduced memory overhead: Results are streamed rather than fully materialized where possible
- DISTINCT operator streaming: DISTINCT queries now stream results, reducing memory usage for large result sets
- Improved operator inlining: Query operators can be inlined into producers for better performance
Shared Statement Cache
Ditto 5.0 introduces a shared statement cache that stores and reuses compiled query plans:- Plan reuse: Compiled query plans are cached and reused for identical or similar statements, eliminating redundant parsing and planning overhead
- Automatic validation: Cached plans are automatically verified and invalidated when collection schemas or system directives change
- Dynamic sizing: The cache automatically resizes based on workload patterns
- Improved large statement performance: Particularly benefits complex queries and larger statements by avoiding expensive re-compilation
These improvements are automatic - no code changes required. Your existing DQL queries will benefit from the enhanced query planner.
Data Sync Performance Improvements
Building on the performance improvements delivered in v4.13 & v4.14, Ditto 5.0 further optimizes data synchronization through tiered blob storage and protocol enhancements, making sync operations faster and more efficient.Whatβs New
Version 5.0 introduces:- Tiered blob storage: Smaller sync updates avoid unnecessary disk I/O by using optimized storage tiers
- Improved session handling: Better avoidance of session resets on reconnection when in-flight updates were lost
- Optimized fsync policy: Document sync avoids forcing files to disk by default, decreasing I/O and improving latency
Impact
Applications upgrading to v5.0 will experience:- Faster sync operations: Reduced disk I/O overhead leads to quicker data synchronization
- Lower latency: Optimized file handling decreases sync latency across the board
- Better reconnection handling: Fewer redundant updates after temporary disconnections
- Improved efficiency: Reduced disk operations lower memory and CPU pressure during sync
Local System Observability
Gain unprecedented visibility into how Ditto operates on your devices. Query real-time metrics, inspect runtime configuration, and monitor system health directly using DQLβgiving you deeper insights than ever before to debug issues, optimize performance, and understand your applicationβs behavior.New Virtual Collections
system:metrics- Query performance metrics and diagnostics in real-time
- Access counters, timers, and other operational metrics via DQL
- Monitor system health and performance without external tools
system:system_info- Query
peer_key,database_id, and configuration settings - Inspect runtime configuration and system parameters
- Useful for debugging and operational awareness
system:shared_statements- Inspect the query plan cache
- View cached statements and their execution plans
- Supports DELETE operations to clear specific cached statements
- Helps optimize query performance and troubleshoot query planning
Local-Only Collections: System collections are local to each peer and are not replicated across the mesh. To query these collections on remote peers, use Remote Query from the Ditto Portal.
Usage Example
Additional Improvements
Reliability & Error Handling
- Enhanced diagnostics: Improved logging when peers receive data that cannot be deserialized
- Recovery mechanisms: Additional recovery paths for document deserialization errors
- Smart log levels: Connection failures start at warning level, escalate to error only after repeated failures
- Panic messages: Filtered to remove internal Rust machinery frames for improved readability
Networking Improvements
- Graceful shutdown: Network connections close cleanly when Ditto is stopped
- Faster disconnection detection: When a peer crashes, Ditto stops attempting to connect within 15 seconds (previously up to 75 minutes)
- mDNS improvements: More reliable mDNS discovery, configurable service names, better address filtering
- BLE improvements: Fixed connection issues on Android 9 and earlier devices
- WebSocket BYOD support: Bring Your Own Discovery now supports WebSocket connections
- Connection cleanup: Fixed deadlock where devices could fail to establish new P2P connections until restarted
DQL Engine Improvements
Beyond the query performance improvements detailed above, v5 includes:- Better error messages: Improved parser error messages for invalid DQL syntax
- Transaction safety: Fixed deadlock scenarios in concurrent transactions
- Index correctness: Fixed issues where index scans could yield incorrect results on document deletion
Logging & Diagnostics
- Better disk utilization: On-disk logs resume writing to incomplete files, making better use of available space
- Compressed size limits: Log file limits now apply to compressed size, significantly increasing retention
- Explicit flushing: Logs explicitly flushed before aborting due to panic
- Virtual collections: New
system:metricsandsystem:system_infocollections for DQL access to metrics and system information
Platform Support
- Linux aarch64: Kotlin SDK now supports ARM64 Linux (Raspberry Pi, AWS Graviton, etc.)
- Swift 6: Full Swift 6 support with Sendable conformance
- 16KB alignment: React Native Android meets Google Playβs November 2025 requirement
Upgrading to v5
Terminology Updates
Ditto 5.0 updates terminology across the platform to align with industry standards and reduce confusion.Database ID (formerly App ID)
appIDβdatabaseIDin all configuration methodsgetAppId()βgetConfig().databaseIdin SDK APIs- Portal and documentation updated to use βDatabase IDβ terminology
Ditto Server (formerly Ditto Cloud)
isConnectedToDittoCloudβisConnectedToDittoServerin presence APIs- Documentation updated to use βDitto Serverβ terminology
Breaking Changes
Ditto 5.0 is a major version release that removes deprecated APIs and legacy features. For migration guidance, see Migration Guidance.Removed APIs
Legacy Query Builder (All SDKs)
All legacy query builder APIs have been removed:store.collection()β Use DQLINSERT,UPDATE,EVICTstatementscollection.find()β Use DQLSELECTqueriescollection.findById()β Use DQL with_idfilter- Live queries β Use DQL observers with
store.registerObserver() - Write transactions β Use
store.transaction()with DQL
Legacy Initialization (All SDKs)
Identityclasses and all subclasses removedDitto(identity:, persistenceDirectory:)constructors removed- Use
DittoConfigfactory methods andDitto.open()instead
Sync Methods Moved
ditto.startSync()βditto.sync.start()ditto.stopSync()βditto.sync.stop()ditto.isSyncActiveβditto.sync.isActive
Other Removals
disableSyncWithV3()- no longer needed, v3 sync removed entirelyAttachmentToken- use dictionary variant- Transport diagnostics APIs - obsolete, removed
- Various deprecated presence properties (
queryOverlapGroup,meshRole, etc.) - Emoji log level headings - setting had no effect, removed
Behavioral Changes
Several default behaviors have changed in v5:- DQL strict mode: Now defaults to
false- no schema definitions required, automatic CRDT type inference - String literals in DQL: Double quotes now delimit strings (not identifiers) for JSON compatibility
- Subscription queries: Reject
LIMITandORDER BYunlessDQL_RESTRICT_SUBSCRIPTION=false - Observer ordering: Observers require explicit
ORDER BYclause for stable ordering - WebSocket sync: Disabled by default in new
TransportConfiginstances - must explicitly enable - Document IDs:
nullis no longer allowed as a document ID
These behavioral changes may affect existing code. Review your DQL queries and subscription logic when migrating to v5.
SDK Size Reduction
The removal of legacy APIs has reduced SDK footprint by approximately 25%, resulting in:- Smaller application binary sizes
- Reduced memory usage
- Faster SDK initialization
- Simpler maintenance and debugging
Migration Guidance
Kotlin Migration Guide
Upgrading to Ditto 5.0 requires updating your initialization code and migrating from legacy query APIs to DQL. The migration process involves:- Updating from
DittoIdentitytoDittoConfig-based initialization - Replacing legacy query builder operations with DQL statements
- Migrating collection observers to DQL observers
- Updating authentication patterns
Kotlin-Specific Changes
The Kotlin SDK has additional platform-specific changes in v5.0 beyond the common breaking changes.Major Architectural Changes
Observer Pattern Migration:store.registerObserver(query, args) { result -> ... }β the change-handler lambda is nowsuspend. The function still returns aDittoStoreObserveryou can close manually.store.observe(query, args) { result -> transform }β NEW. Returns aFlow<T>that requires atransformlambda. Collecting it in aCoroutineScope(e.g.lifecycleScope) auto-cancels the observer when the scope ends.store.collect(query, args) { result -> ... }β NEW. A suspending function that delivers events to your handler and signals Ditto for the next event only after the handler returns, providing back-pressure via coroutine suspension.- Presence observers:
presence.observe(handler)βpresence.observe(): Flow<DittoPresenceGraph>β collect in a scope. - Disk-usage observers:
diskUsage.observe(handler): CloseableβdiskUsage.observe(): Flow<DittoDiskUsageItem>β collect in a scope. - Transport-condition observers:
ditto.callback = DittoCallback { ... }βditto.transportCondition: Flow<DittoTransportConditionEvent>.
DittoAndroidConfigβDittoConfig(no longer requires context parameter)DittoConfig.Connect.Servernow takesStringinstead ofURI- Import package changed from
live.ditto.*tocom.ditto.kotlin.*
- DQL query arguments can be passed as a plain
Map<String, Any?>, aDittoCborSerializable.Dictionary, or any@Serializabledata class. DittoQueryResultItem.valueis nowDittoCborSerializable.Dictionary(wasMap<String, Any>). Read field values with typed accessors β for exampleitem.value["color"].string,.stringOrNull,.intOrNull,.longOrNull,.doubleOrNull,.booleanOrNull,.listOrNull,.dictionaryOrNull,.attachmentTokenOrNull,.isNullβ or callitem.jsonString()and decode withkotlinx.serialization.- The cast
item.value["color"] as Stringno longer works becausevalue[key]returns aDittoCborSerializable, notAny.
- Internal types under
com.ditto.internal.*are no longer part of the public Kotlin API surface β onlycom.ditto.kotlin.*is supported for application code. DittoExperimental-annotated APIs are no longer publicly exposed.DittoPanicExceptionis a new exception type raised when the native core panics (requiresDittoConfig.Experimental(coreExceptionBridgingEnabled = true)to surface as a JVM exception).
Platform Support
Linux ARM64:- Added Linux aarch64 platform support
- Enables deployment on ARM64-based Linux devices such as Raspberry Pi and AWS Graviton instances
Android: Next Generation Networking
WiFi Aware Transport:- Added Next Generation Networking UDP transport on WiFi Aware
- Transport only attempts to start on devices that support it
- Respects system data path limits to prevent resource exhaustion
- Never crashes even when required permissions are missing
- Added configuration option to reset device WiFi stack in Android profile owner scenarios for improved reliability
API Changes
Query Arguments:DittoSyncSubscription.queryArgumentsno longer guaranteed to be strictly equal to original arguments due to serialization roundtrip- Added
queryArgumentsCborDataandqueryArgumentsJsonStringproperties for custom deserialization - Added
DittoSync.registerSubscription()overload accepting serializable arguments with accompanying serializer
- Flow-based
DittoStoreObserver.observe()refactored to requiretransformfunction that doesnβt returnDittoQueryResult - Suspend-based
DittoStoreObserver.observe()renamed tocollect() - Restored
DittoStoreObserver.registerObserver()function in Kotlin DittoQueryResultautomatically closed byDittoStoreObserverto prevent leaking
- Renamed
DittoConnection.getPeer{1,2}Key()togetPeer{1,2}() - Renamed
DittoConnectionRequest.getPeerKeyString()togetPeerKey() DittoPeer.osnow returnsDittoPeerOs?enum instead ofString?for type-safe OS representation- Renamed
isConnectedToDittoCloudtoisConnectedToDittoServer
- Changed
Ditto.startSync()moved toDittoSync.start()- Useditto.sync.start()instead - Changed
Ditto.stopSync()moved toDittoSync.stop()- Useditto.sync.stop()instead - Changed
Ditto.isSyncActivemoved toDittoSync.isActive- Useditto.sync.isActiveinstead
- Removed
DittoAuthenticationCallbackinterface andDittoAuthenticator.setCallback()method - Use
DittoAuthenticator.expirationHandlerproperty with lambda instead - Fixed clientInfo not being returned on auth failure (#SDKS-1656)
- Added
Ditto.smallPeerInfoproperty for configuring metadata shared with other peers during synchronization
- Removed
DittoLogger.isEmojiLogLevelHeadingsEnabled- Setting had no effect since 4.8.0
- Removed
ditto.sdkVersion- Use staticDitto.VERSIONinstead
Memory Management
Resource Cleanup:- Fixed memory leak in
DittoQueryResult.close()which now also closesDittoQueryResultIteminstances - Eliminates need for manual cleanup and resolves excessive memory usage with large query results
- Removed
Ditto.runGarbageCollection()- Memory now fully managed using AutoCloseable types
Migration Example
The initialization flow has changed fromDittoIdentity-based to DittoConfig-based patterns. Hereβs how to migrate:Accessing Configuration at Runtime
After initialization, you can read the persistence directory from the [Ditto] instance. The database ID is the same value you passed when constructingDittoConfig, so keep it in
your own configuration object if you need it later.Migration Path
All legacy query operations have direct DQL equivalents:Update Operations:Complete migration examples for all legacy query patterns are available in the Legacy to DQL Migration Guide.
How It Works
The key difference is whether you need to explicitly define MAP types for objects:Migration
Update your code to use the new terminology:Kotlin Specific Changes
Fixed:- WiFi Aware transport will only attempt to be started on devices that support it (#18484)
- WiFi Aware should never crash even when required permissions are missing (#18705)
DittoQueryResultis automatically closed byDittoStoreObserverto prevent it from leaking (#19214)- BLE platform GATT disconnect watchdog restart loop on Android when remote peers disconnect or connectGatt fails (#21687)
WifiAwarePlatformno longer blocks the first reset attempt after initialization (#NETW-1399)- Memory leak in
DittoQueryResult.close(), which now also closes itsDittoQueryResultIteminstances. This eliminates the need for manual cleanup and resolves excessive memory usage with large query results (#SDKS-1463) clientInfonot returned on auth failure (#SDKS-1656)WifiAwarePlatformno longer callsgetNumberOfSupportedDataPaths()on Android versions below API 33, preventingNoSuchMethodErroron Android 12 devices (#SDKS-2753)DittoDiskUsageItem.sizeInBytesnow usesLonginstead ofIntto support sizes greater than 2.1GB (#SDKS-2854)- Potential crashes on 32-bit ARM Android devices (e.g., Samsung SM-T380 running Android 9) (#SDKS-3118)
- Moved BLE operations off the main thread to prevent ANR exceptions (#SPO-139)
NoClassDefFoundErrorcrash during Ditto initialization in production builds (#SPO-379)
- The
queryArgumentsproperty onDittoSyncSubscriptions is no longer guaranteed to be strictly equal to the original arguments passed when registering the sync subscription. This is due to a serialization roundtrip, which may affect equality checks, particularly for non-primitive values. If you want to decode query arguments into a specific type, then use thequeryArgumentsCborDataorqueryArgumentsJsonStringproperties now available onDittoSyncSubscriptioninstances and decode things as required (#17003) - WiFi Aware now respects system data path limits to prevent resource exhaustion (#18503)
- The
Flow-basedDittoStoreObserver.observe()function is refactored to force the user to provide atransformfunction that doesnβt return theDittoQueryResult(#19214) - The
queryArgumentsproperty onDittoStoreObservers is no longer guaranteed to be strictly equal to the original arguments passed when registering the store observer. This is due to a serialization roundtrip, which may affect equality checks, particularly for non-primitive values. If you want to decode query arguments into a specific type, then use thequeryArgumentsCborDataorqueryArgumentsJsonStringproperties now available onDittoStoreObserverinstances and decode things as required (#CORE-862) - Renamed
DittoConnection.getPeer{1,2}Key()togetPeer{1,2}()(#SDKS-1184) - Renamed
DittoConnectionRequest.getPeerKeyString()togetPeerKey()(#SDKS-1184) - Nested all
DittoTransportConfigsub-config classes (PeerToPeer,Connect,Listen,Global,BluetoothLE,Lan,WifiAware,TcpConfig,HttpConfig) inside their parent classes with shortened names (#SDKS-1674) Ditto.startSync()moved toDittoSync.start(). Useditto.sync.start()instead (#SDKS-1875)Ditto.stopSync()moved toDittoSync.stop(). Useditto.sync.stop()instead (#SDKS-1875)Ditto.isSyncActivemoved toDittoSync.isActive. Useditto.sync.isActiveinstead (#SDKS-1875)- Presence graph Peers have renamed the
isConnectedToDittoCloudproperty toisConnectedToDittoServerto clarify that it reflects a connection to any Big Peer rather than specifically those in the Ditto cloud service (#SDKS-2188) - Default persistence directory now uses platform-standard locations:
%LOCALAPPDATA%on Windows (with%ProgramData%for system services),$XDG_DATA_HOMEon Linux, unchanged on macOS. Apps with existing local data can setpersistenceDirectoryexplicitly to preserve prior behavior (#SDKS-2464) DittoPeer.osproperty now returnsDittoPeerOs?enum instead ofString?for type-safe operating system representation (#SDKS-2619)DittoStore.execute()andDittoTransaction.execute()are now scoped APIs that auto-closeDittoQueryResult. The previousexecute()that returned a rawDittoQueryResulthas been renamed toexecuteRaw()(#SDKS-2825)DittoExceptionpackage fromcom.ditto.kotlin.errortocom.ditto.kotlin, aligning with the Java APIβscom.ditto.java.DittoException(#SDKS-2855)
queryArgumentsCborDataandqueryArgumentsJsonStringproperties toDittoSyncSubscriptioninstances. If you want to decode query arguments into a specific type, then use these and decode things as required (#17003)- An overload of
DittoSyncβsregisterSubscriptionmethod that takes anargumentsvalue that is serializable and an accompanying serializer (#17003) - Next Generation Networking UDP transport on WiFi Aware (#18045)
- The new
transport_tcp_client_android_bind_interfacesystem parameter which enables use of multiple NICs on Android (#20782) queryArgumentsCborDataandqueryArgumentsJsonStringproperties toDittoStoreObserverinstances. If you want to decode query arguments into a specific type, then use these and decode things as required (#CORE-862)- WiFi Aware transport configuration option to reset the device WiFi stack in Android profile owner scenarios, improving reliability (#NETW-435)
- API overloads on
DittoStore,DittoTransaction, andDittoSyncfunctions that take aDittoCborSerializable.Dictionaryargument, so that an arbitrary map can be passed instead (#SDKS-1507) DittoSyncSubscription.queryArgumentsCborDataproperty for accessing query arguments as a CBOR-encoded byte array (#SDKS-2007)DittoSyncSubscription.queryArgumentsJsonStringproperty for accessing query arguments as a JSON-encoded string (#SDKS-2007)- Linux aarch64 platform support for the Java SDK, enabling deployment on ARM64-based Linux devices such as Raspberry Pi and AWS Graviton instances (#SDKS-2463)
- Diff-aware overloads of
DittoStore.registerObserver(),DittoStore.collect(), andDittoStore.observe()that deliver aDittoDiffalongside theDittoQueryResult. The differ lifecycle is tied to the observer β closing the observer frees the differ automatically (#SDKS-3002) DittoPresence.setPeerMetadata(Map<String, Any?>)convenience method for setting peer metadata from a plain map, matching the ergonomic pattern used byDittoStore.execute()(#SDKS-3021)DittoJsonSerializable.ObjectValue.toMap()method that recursively unwraps toMap<String, Any?>(#SDKS-3021)DittoJsonSerializable.toAny()method that converts any JSON value to its plain Kotlin equivalent (#SDKS-3021)
DittoTransportConfig()constructor andDittoTransportConfig.Builder()no-arg constructor, which create a transport config with all transports disabled. PreferDitto.updateTransportConfig()to modify the existing transport configuration (#SDKS-2787)
Ditto.runGarbageCollection(). Memory is now fully managed using AutoClosable types in the SDK (#SDKS-1851)DittoLogger.isEmojiLogLevelHeadingsEnabled. The setting had no effect since 4.8.0 (#SDKS-1886)ditto.sdkVersion. Use staticDitto.VERSIONinstead (#SDKS-2105)Ditto.getTransportDiagnostics(),DittoTransportDiagnostics,DittoTransportSnapshot, andTransportExceptionReason.FailedToDecodeTransportDiagnostics, as these APIs are now obsolete (#SDKS-2368)- Support for Intel Macs (#SDKS-2459)
DittoSmallPeerInfoSyncScopeenum andDittoSmallPeerInfo.syncScopeproperty. Sync scopes are now controlled via DQLALTER SYSTEMcommands (#SDKS-2683)