Migration
Couchbase Lite 4.0.0 introduces several important updates and API changes to simplify development and ensure consistency across all platforms. Most APIs deprecated in versions 3.1.0 through 3.3.x have now been removed, and several configurations have been updated or restructured.
This guide provides step-by-step instructions for upgrading from Couchbase Lite 3.2.x / 3.3.x to 4.0.0. Follow these guidelines to ensure a smooth migration with minimal disruption to your applications.
Document Operations on the Default Collection
Starting in Couchbase Lite 3.1.0, scope and collections were introduced to better organize documents within a database. At that time, document operations performed directly through the Database object for the default collection, such as get, save, delete, purge, count, set or get expiration, index management, and listeners, were deprecated.
In 4.0.0, all deprecated document APIs in the Database class have been removed. To work with documents in the default collection, retrieve the default collection from the database and use the collection’s methods instead.
-
Swift
-
Objective-C
-
Java
-
C#
-
C
// Before
database.save(document)
// After
let collection = try database.defaultCollection()
try collection.save(document: document)
// Before
[database saveDocument: document error: &error];
// After
CBLCollection* collection = [database defaultCollection: &error];
[collection saveDocument: document error: &error];
// Before
database.save(document);
// After
Collection collection = database.getDefaultCollection();
collection.save(document);
// Before
database.Save(document);
// After
var collection = database.GetDefaultCollection();
collection.Save(document);
// Before
CBLDatabase_SaveDocument(database, document, &error);
// After
CBLCollection* collection = CBLDatabase_DefaultCollection(database, &error);
CBLCollection_SaveDocument(collection, document, &error);
ReplicatorConfiguration and CollectionConfiguration
When collections were introduced in 3.1.0, the ReplicatorConfiguration constructor that accepted a Database for the default collection was deprecated. A new API was introduced to let developers explicitly add collections to the ReplicatorConfiguration, each with an optional CollectionConfiguration as needed.
In 4.0.0, the old constructor and related APIs have been removed. Collections must now be specified up front when creating a ReplicatorConfiguration. They can no longer be added or removed dynamically. This ensures consistency and prevents accidental inclusion of the default collection, which previously caused confusion or replication errors when not present on Sync Gateway.
-
Swift
-
Objective-C
-
Java
-
C
// Before
let config = ReplicatorConfiguration(database, target: endpoint)
// After
let collections = [defaultCollection].map { CollectionConfiguration(collection: $0) }
let config = ReplicatorConfiguration(collections: collections, target: endpoint)
// Before (with database)
CBLReplicatorConfiguration* config =
[[CBLReplicatorConfiguration alloc] initWithDatabase: database
target: endpoint];
// After
CBLCollectionConfiguration* colConfig =
[[CBLCollectionConfiguration alloc] initWithCollection: defaultCollection];
CBLReplicatorConfiguration* config =
[[CBLReplicatorConfiguration alloc] initWithCollections: @[colConfig]
target: endpoint];
// Before
ReplicatorConfiguration config = new ReplicatorConfiguration(database, endpoint);
// After
CollectionConfiguration colConfig = new CollectionConfiguration(defaultCollection);
ReplicatorConfiguration config = new ReplicatorConfiguration(Set.of(colConfig), endpoint);
// Before
CBLReplicatorConfiguration config = {
.database = database,
.endpoint = endpoint,
// ... other fields
};
// After
CBLCollectionConfiguration colConfig = {
.collection = defaultCollection
};
CBLReplicatorConfiguration config = {
.collections = &colConfig,
.collectionCount = 1,
.endpoint = endpoint,
// ... other fields
};
CBLReplicatorConfiguration Changes in Couchbase Lite C
Couchbase Lite C uses a struct-based configuration and has never supported dynamic collection management. In version 4.0.0, several updates were made to align the API with other platforms:
-
The deprecated database property for the default collection in CBLReplicatorConfiguration has been removed.
-
The configuration fields have been reordered so that the required fields—collections, collectionCount, and endpoint—appear first, followed by commonly used fields such as replicatorType, authenticator, context, and pinnedServerCertificate.
-
CBLReplicatorCollection is now defined as an alias of CBLCollectionConfiguration. Developers are encouraged to use CBLCollectionConfiguration going forward, as CBLReplicatorCollection is not deprecated but will be removed in the next major release.
Developers who create configurations using positional initialization should review and update the field order according to the latest ReplicatorConfiguration documentation.
Furthermore, the deprecated propertyEncryptor and propertyDecryptor callbacks, previously used for encrypting and decrypting document properties in the default collection, have been removed.
Developers should use the new documentPropertyEncryptor and documentPropertyDecryptor callbacks instead, which provide consistent and extensible encryption support across collections.
.NET Configuration API
Couchbase Lite 4.0 modernizes its .NET configuration model to make configuration objects immutable and type-safe, leveraging new C# language features:
-
It uses C# 9 init-only properties and C# 11 required properties to enforce immutability and ensure that all mandatory fields are set at creation time.
-
Constructors now require all required properties, while optional properties can be set only during initialization.
-
The required properties are also declared as init-only to allow modifications when cloning an existing configuration using the C# 'with' keyword.
This design ensures configuration objects remain predictable, reusable, and safe from unintended changes.
// Before
var config = new ReplicatorConfiguration(endpoint);
config.AddCollection(DefaultCollection);
config.ReplicatorType = ReplicatorType.Push;
// Copy
var updated = new ReplicatorConfiguration(config);
updated.ReplicatorType = ReplicatorType.Pull;
// After
var collections = CollectionConfiguration.FromCollections(DefaultCollection);
var config = new ReplicatorConfiguration(collections, endpoint) {
ReplicatorType = ReplicatorType.Push
};
// Copy immutably using 'with'
var updated = config with { ReplicatorType = ReplicatorType.Pull };
Replication Pending Documents
The replication pending documents API applies only to documents that are waiting to be pushed to the remote endpoint. The deprecated methods for retrieving or checking pending documents without specifying a collection have been removed in 4.0.0. Use the corresponding methods that require a Collection object instead.
-
Swift
-
Objective-C
-
Java
-
C#
-
C
// Before
let pendings = try replicator.pendingDocumentIds()
let isPending = try replicator.isDocumentPending("doc1")
// After
let defaultCollection = try database.defaultCollection()
let pendings = try replicator.pendingDocumentIds(collection: defaultCollection)
let isPending = try replicator.isDocumentPending("doc1", collection: defaultCollection)
// Before
NSArray* pendings = [replicator pendingDocumentIds: &error];
BOOL isPending = [replicator isDocumentPending: @"doc1" error: &error];
// After
CBLCollection* defaultCollection = [database defaultCollection: &error];
NSArray* pendings = [replicator pendingDocumentIDsForCollection: defaultCollection
error: &error];
BOOL isPending = [replicator isDocumentPending: @"doc1"
collection: defaultCollection
error: &error];
// Before
Set<String> pendings = replicator.getPendingDocumentIds();
boolean isPending = replicator.isDocumentPending("doc1");
// After
Collection defaultCollection = database.getDefaultCollection();
Set<String> pendings = replicator.getPendingDocumentIds(defaultCollection);
boolean isPending = replicator.isDocumentPending("doc1", defaultCollection);
// Before
var pendings = replicator.GetPendingDocumentIds();
var isPending = replicator.IsDocumentPending("doc1");
// After
var defaultCollection = database.GetDefaultCollection();
var pendings = replicator.GetPendingDocumentIds(defaultCollection);
var isPending = replicator.IsDocumentPending("doc1", defaultCollection);
// Before
FLDict pendings = CBLReplicator_PendingDocumentIDs(replicator, &error);
bool isPending = CBLReplicator_IsDocumentPending(replicator, "doc1", &error);
// After
CBLCollection* defaultCollection = CBLDatabase_DefaultCollection(database, &error);
FLDict pendings = CBLReplicator_PendingDocumentIDs(replicator, defaultCollection, &error);
bool isPending = CBLReplicator_IsDocumentPending(replicator, "doc1", defaultCollection, &error);
Endpoint Listener Configuration
The deprecated constructors for URLEndpointListenerConfiguration and MessageEndpointListenerConfiguration that accepted a Database object for the default collection have been removed in 4.0.0. Use constructors that accept one or more Collection objects instead.
-
Swift
-
Objective-C
-
Java
-
C#
-
C
// Before
let config1 = URLEndpointListenerConfiguration(database: database)
let config2 = MessageEndpointListenerConfiguration(database: database,
protocolType: .messageStream)
// After
let defaultCollection = try database.defaultCollection()
let config1 = URLEndpointListenerConfiguration(collections: [defaultCollection])
let config2 = MessageEndpointListenerConfiguration(collections: [defaultCollection],
protocolType: .messageStream)
// Before
CBLURLEndpointListenerConfiguration* config1 =
[[CBLURLEndpointListenerConfiguration alloc] initWithDatabase: database];
CBLMessageEndpointListenerConfiguration* config2 =
[[CBLMessageEndpointListenerConfiguration alloc] initWithDatabase: database
protocolType: kCBLProtocolTypeMessageStream];
// After
CBLCollection* defaultCollection = [database defaultCollection: &error];
CBLURLEndpointListenerConfiguration* config1 =
[[CBLURLEndpointListenerConfiguration alloc] initWithCollections: @[defaultCollection]];
CBLMessageEndpointListenerConfiguration* config2 =
[[CBLMessageEndpointListenerConfiguration alloc] initWithCollections: @[defaultCollection]
protocolType: kCBLProtocolTypeMessageStream];
// Before
URLEndpointListenerConfiguration config1 =
new URLEndpointListenerConfiguration(database);
MessageEndpointListenerConfiguration config2 =
new MessageEndpointListenerConfiguration(database, ProtocolType.MESSAGE_STREAM);
// After
Collection defaultCollection = database.getDefaultCollection();
URLEndpointListenerConfiguration config1 =
new URLEndpointListenerConfiguration(Set.of(defaultCollection));
MessageEndpointListenerConfiguration config2 =
new MessageEndpointListenerConfiguration(Set.of(defaultCollection),
ProtocolType.MESSAGE_STREAM);
// Before
var config1 = new URLEndpointListenerConfiguration(database);
var config2 = new MessageEndpointListenerConfiguration(database, ProtocolType.MessageStream);
// After
var defaultCollection = database.GetDefaultCollection();
var config1 = new URLEndpointListenerConfiguration(new[] { defaultCollection });
var config2 = new MessageEndpointListenerConfiguration(new[] { defaultCollection },
ProtocolType.MessageStream);
// Before
CBLURLEndpointListenerConfiguration config1 = {
.database = database,
// ... other fields
};
// After
CBLCollection* defaultCollection = CBLDatabase_DefaultCollection(database, &error);
CBLURLEndpointListenerConfiguration config1 = {
.collections = &defaultCollection,
.collectionCount = 1,
// ... other fields
};
TLSIdentity Creation
Introduced in 3.3.0 for iOS and Android, MultipeerReplicator requires a TLSIdentity that supports both client and server authentication.
In 4.0.0, the older methods for creating separate client-only or server-only identities have been removed to improve API consistency and prevent confusion. Use the unified API with the KeyUsages flag, which allows you to specify whether the identity is for the client, the server, or both.
|
Couchbase Lite C has provided only the unified API since version 3.2.3. |
-
Swift
-
Objective-C
-
Java
-
C#
// Before
let identity = TLSIdentity.createIdentity(forServer: server,
attributes: certAttrs,
label: "identity1")
// After
let identity = TLSIdentity.createIdentity(for: .serverAuth,
attributes: certAttrs,
label: "identity1")
// Before
CBLTLSIdentity* identity = [CBLTLSIdentity createIdentityForServer: YES
attributes: certAttrs
label: @"identity1"
error: &error];
// After
CBLTLSIdentity* identity = [CBLTLSIdentity createIdentityForKeyUsages: kCBLKeyUsagesServerAuth
attributes: certAttrs
label: @"identity1"
error: &error];
// Before
TLSIdentity identity = TLSIdentity.createIdentity(true, certAttrs, "identity1");
// After
TLSIdentity identity = TLSIdentity.createIdentity(Set.of(KeyUsage.SERVER_AUTH), certAttrs, "identity1");
// Before
var identity = TLSIdentity.CreateIdentity(true, certAttrs, "identity1");
// After
var identity = TLSIdentity.CreateIdentity(KeyUsage.ServerAuth, certAttrs, "identity1");
Logging API
Since 3.2.2, Couchbase Lite has provided an immutable LogSink API for log configuration. In 4.0.0, the old logging APIs have been removed, leaving LogSink as the single supported log configuration API.
-
Swift
-
Objective-C
-
Java
-
C#
-
C
// Before
// Console
Database.log.console.level = .verbose
// File
var config = LogFileConfiguration(directory: logDir)
Database.log.file.config = config
Database.log.file.level = .verbose
// Custom
Database.log.custom = MyCustomLogger()
// After
// Console
LogSinks.console = ConsoleLogSink(level: .verbose)
// File
LogSinks.file = FileLogSink(level: .verbose, directory: logDir)
// Custom
LogSinks.file = CustomLogSink(level: .verbose, logSink: myCustomLogSink)
// Before
// Console
CBLDatabase.log.console.level = kCBLLogLevelVerbose;
// File
CBLLogFileConfiguration* config = [[CBLLogFileConfiguration alloc] initWithDirectory: logDir];
CBLDatabase.log.file.config = config;
CBLDatabase.log.file.level = kCBLLogLevelVerbose;
// Custom
CBLDatabase.log.custom = myCustomLogger;
// After
// Console
CBLLogSinks.console = [[CBLConsoleLogSink alloc] initWithLevel: kCBLLogLevelVerbose];
// File
CBLLogSinks.file = [[CBLFileLogSink alloc] initWithLevel: kCBLLogLevelVerbose
directory: logDir];
// Custom
CBLLogSinks.custom = [[CBLCustomLogSink alloc] initWithLevel: kCBLLogLevelVerbose
logSink: myCustomLogSink];
// Before
// Console
Database.log.getConsole().setLevel(LogLevel.VERBOSE);
// File
LogFileConfiguration config = new LogFileConfiguration(logDir);
Database.log.getFile().setConfig(config);
Database.log.getFile().setLevel(LogLevel.VERBOSE);
// Custom
Database.log.setCustom(new MyCustomLogger());
// After
// Console
LogSinks.get().setConsole(new ConsoleLogSink(LogLevel.VERBOSE, LogDomain.ALL));
// File
LogSinks.get().setFile(
new FileLogSink.Builder()
.setDirectory(logDir)
.setLevel(LogLevel.VERBOSE)
.build()
);
// Custom
LogSinks.get().setCustom(new BaseLogSink(LogLevel.VERBOSE) {
@Override
protected void writeLog(LogLevel level, LogDomain domain, String message) {
// Your custom logging implementation
}
});
// Before
// Console
Database.Log.Console.Level = LogLevel.Verbose;
// File
var config = new LogFileConfiguration(logDir);
Database.Log.File.Config = config;
Database.Log.File.Level = LogLevel.Verbose;
// Custom
Database.Log.Custom = new MyCustomLogger();
// After
// Console
LogSinks.Console = new ConsoleLogSink(LogLevel.Verbose);
// File
LogSinks.File = new FileLogSink(LogLevel.Verbose, logDir);
// Custom
LogSinks.Custom = new CustomLogSink(LogLevel.Verbose, myCustomLogSink);
// Before
// Console
CBLLog_SetConsoleLevel(kCBLLogVerbose);
// File
CBLLogFileConfiguration config = {
.level = kCBLLogVerbose,
.directory = logDir
};
CBLLog_SetFileConfig(&config);
// Custom
CBLLog_SetCallback(myCustomLogCallback);
// After
// Console
CBLConsoleLogSink* consoleSink = CBLConsoleLogSink_Create(kCBLLogVerbose);
CBLLogSinks_SetConsole(consoleSink);
// File
CBLFileLogSink* fileSink = CBLFileLogSink_Create(kCBLLogVerbose, logDir);
CBLLogSinks_SetFile(fileSink);
// Custom
CBLCustomLogSink* customSink = CBLCustomLogSink_Create(kCBLLogVerbose, myCustomLogCallback);
CBLLogSinks_SetCustom(customSink);
QueryBuilder and Expression Changes
Several QueryBuilder APIs have been updated or removed in 4.0.0 to improve consistency with Couchbase Server SQL++ syntax.
DataSource
The DataSource.database() expression for the default collection has been removed.
Use DataSource.collection() instead.
-
Swift
-
Java
-
C#
// Before
let query = QueryBuilder
.select(SelectResult.all())
.from(DataSource.database(database))
// After
let collection = try database.defaultCollection()
let query = QueryBuilder
.select(SelectResult.all())
.from(DataSource.collection(collection))
// Before
Query query = QueryBuilder
.select(SelectResult.all())
.from(DataSource.database(database));
// After
Collection collection = database.getDefaultCollection();
Query query = QueryBuilder
.select(SelectResult.all())
.from(DataSource.collection(collection));
// Before
var query = QueryBuilder
.Select(SelectResult.All())
.From(DataSource.Database(database));
// After
var collection = database.GetDefaultCollection();
var query = QueryBuilder
.Select(SelectResult.All())
.From(DataSource.Collection(collection));
Expressions
The deprecated isNullOrMissing() and notNullOrMissing() functions have been removed.
Use isNotValued() and isValued() instead, consistent with Couchbase Server SQL++ syntax.
-
Swift
-
Java
-
C#
// Before
let query = QueryBuilder
.select(SelectResult.all())
.from(DataSource.collection(collection))
.where(Expression.property("name").isNullOrMissing())
// After
let query = QueryBuilder
.select(SelectResult.all())
.from(DataSource.collection(collection))
.where(Expression.property("name").isNotValued())
// Before
Query query = QueryBuilder
.select(SelectResult.all())
.from(DataSource.collection(collection))
.where(Expression.property("name").isNullOrMissing());
// After
Query query = QueryBuilder
.select(SelectResult.all())
.from(DataSource.collection(collection))
.where(Expression.property("name").isNotValued());
// Before
var query = QueryBuilder
.Select(SelectResult.All())
.From(DataSource.Collection(collection))
.Where(Expression.Property("name").IsNullOrMissing());
// After
var query = QueryBuilder
.Select(SelectResult.All())
.From(DataSource.Collection(collection))
.Where(Expression.Property("name").IsNotValued());
Full-Text Search
Full-text search APIs have been updated in 4.0.0:
-
FullTextExpressionis removed; useFullTextFunctioninstead. -
rank()andmatch()overloads that accept a string index name are removed; use overloads that accept anIndexExpressionProtocol/IndexExpressionInterface.
-
Swift
-
Java
-
C#
// Before
let fts = FullTextExpression.index("ftsIndex")
let query = QueryBuilder
.select(SelectResult.expression(Meta.id))
.from(DataSource.collection(collection))
.where(fts.match("search term"))
// After
let fts = FullTextFunction.index("ftsIndex")
let query = QueryBuilder
.select(SelectResult.expression(Meta.id))
.from(DataSource.collection(collection))
.where(FullTextFunction.match(fts, query: "search term"))
// Before
FullTextExpression fts = FullTextExpression.index("ftsIndex");
Query query = QueryBuilder
.select(SelectResult.expression(Meta.id))
.from(DataSource.collection(collection))
.where(fts.match("search term"));
// After
IndexExpression fts = Expression.fullTextIndex("ftsIndex");
Query query = QueryBuilder
.select(SelectResult.expression(Meta.id))
.from(DataSource.collection(collection))
.where(FullTextFunction.match(fts, "search term"));
// Before
var fts = FullTextExpression.Index("ftsIndex");
var query = QueryBuilder
.Select(SelectResult.Expression(Meta.ID))
.From(DataSource.Collection(collection))
.Where(fts.Match("search term"));
// After
var fts = FullTextFunction.Index("ftsIndex");
var query = QueryBuilder
.Select(SelectResult.Expression(Meta.ID))
.From(DataSource.Collection(collection))
.Where(FullTextFunction.Match(fts, "search term"));
Vector Search Compatibility
Couchbase Lite 4.0.0 is compatible only with Vector Search 2.0.0, due to dependency and build process changes.
If you are using Vector Search in your application, you must upgrade to Vector Search 2.0.0 when migrating to Couchbase Lite 4.0.0.
For more information about Vector Search 2.0.0, see the Vector Search release notes.
Summary of Migration Actions
The following table summarizes the key migration actions required when upgrading to Couchbase Lite 4.0.0:
| Area | Action |
|---|---|
Document operations |
Use Collection APIs instead of Database for the default collection |
ReplicatorConfiguration |
Define collections up front using CollectionConfiguration for each collection. Remove dynamic add/remove logic. |
Couchbase Lite C |
Update struct field order if using positional initialization. |
.NET Configuration |
Adopt immutable, init-only configuration model. |
Replication pending documents |
Use collection-specific APIs. |
Endpoint Listener configuration |
Use constructors that accept collections |
TLSIdentity |
Use unified creation API with KeyUsages flag |
Logging |
Configure logs via immutable LogSink API |
QueryBuilder |
Replace removed DataSource, expression, and FTS functions |
Vector Search |
Upgrade to Vector Search 2.0.0 |