Migrate from KMongo
On this page
Overview
This page contains a high-level comparison of most of the ways the official MongoDB Kotlin and the community-developed KMongo driver differ. You can use this page to identify the changes you need to make to migrate from the deprecated KMongo driver to the official MongoDB Kotlin driver.
KMongo is a popular community-developed library for working with MongoDB from Kotlin applications. It is a wrapper around the Java driver that was created prior to the creation of the official Kotlin driver to serve the needs of the Kotlin community.
Important
As of July 2023, KMongo has been marked as deprecated.
The MongoDB Kotlin driver is the officially supported and maintained MongoDB driver for Kotlin. It is developed by the MongoDB team.
Although both drivers support synchronous and asynchronous operations, the examples on this page will use asynchronous coroutine-based operations.
Connect to MongoDB Cluster
Both drivers let you connect to and communicate with MongoDB clusters from a Kotlin application.
To connect to a MongoDB cluster using the MongoDB Kotlin driver:
import com.mongodb.kotlin.client.coroutine.MongoClient data class Jedi(val name: String, val age: Int) // Replace the placeholder with your MongoDB deployment's connection string val uri = CONNECTION_STRING_URI_PLACEHOLDER val mongoClient = MongoClient.create(uri) val database = mongoClient.getDatabase("test") // Get a collection of documents of type Jedi val collection = database.getCollection<Jedi>("jedi")
See the Connect to MongoDB documentation for more information.
To connect to a MongoDB cluster using KMongo with coroutines:
import org.litote.kmongo.reactivestreams.* import org.litote.kmongo.coroutine.* data class Jedi(val name: String, val age: Int) // Get new MongoClient instance using coroutine extension val client = KMongo.createClient().coroutine val database = client.getDatabase("test") // Get a collection of documents of type Jedi val col = database.getCollection<Jedi>()
Unlike the MongoDB Kotlin driver, KMongo allows the collection name to be inferred from the data class name.
CRUD and Aggregation
Both drivers provide support for all MongoDB CRUD APIs and aggregation operations.
The MongoDB Kotlin driver also provides functions for all basic CRUD operations:
// Insert a document val jedi =a Jedi("Luke Skywalker", 19) collection.insertOne(jedi) // Find a document val luke = collection.find(Jedi::name.name, "Luke Skywalker") val jedis = collection.find(lt(Jedi::age.name, 30)).toList() // Update a document val filter = Filters.eq(Jedi::name.name, "Luke Skywalker") val update = Updates.set(Jedi::age.name, 20) collection.updateOne(filter, update) // Delete a document val filter = Filters.eq(Jedi::name.name, "Luke Skywalker") collection.deleteOne(filter)
Aggregation pipelines can be built using the aggregate
method and the
pipeline
function:
data class Results(val avgAge: Double) val resultsFlow = collection.aggregate<Results>( listOf( Aggregates.match(Filters.ne(Jedi::name.name, "Luke Skywalker")), Aggregates.group("\$${Jedi::name.name}", Accumulators.avg("avgAge", "\$${Jedi::age.name}")) ) ) resultsFlow.collect { println(it) }
See the CRUD Operations and Aggregation documentation for more information.
KMongo provides functions for all basic CRUD operations:
// Insert a document val jedi = Jedi("Luke Skywalker", 19) col.insertOne(jedi) // Find a document val luke = col.findOne(Jedi::name eq "Luke Skywalker") val jedis = col.find(Jedi::age lt 30).toList() // Update a document col.updateOne(Jedi::name eq "Luke Skywalker", setValue(Jedi::age, 20)) // Delete a document col.deleteOne(Jedi::name eq "Luke Skywalker")
Aggregation pipelines can be built using the aggregate
method and the
pipeline
function:
val avgAge = collection.aggregate<Double>( pipeline( match(Jedi::name ne "Luke Skywalker"), group(Jedi::name, avg(Jedi::age)) ) ).toList()
For more information on available methods, see the Extensions Overview KMongo documentation.
Construct Queries
Both drivers provide support for type-safe queries using property references.
The MongoDB Kotlin driver uses the Builders API to construct queries.
Alternatively, you can use the Document
class.
data class Person(val name: String, val email: String, val gender: String, val age: Int) data class Results(val email: String) val collection = database.getCollection<Person>("people") // Using Builders val filter = and(eq("gender", "female"), gt("age", 29)) val projection = fields(excludeId(), include("email")) val results = collection.find<Results>(filter).projection(projection) // Using Document class val filter = Document().append("gender", "female").append("age", Document().append("\$gt", 29)) val projection = Document().append("_id", 0).append("email", 1) val results = collection.find<Results>(filter).projection(projection)
To map a KMongo string query to the Kotlin driver, you can use the JsonObject
class.
val query = JsonObject("{\"name\": \"Gabriel Garc\\u00eda M\\u00e1rquez\"}") val jsonResult = collection.find(query).firstOrNull()
For more information, see the following Kotlin driver documentation:
Documents guide
JsonObject API Documentation
With KMongo, you can create queries using property references on the data class that represents objects in a collection and infix operators that the library provides.
data class Jedi(val name: String) val yoda = col.findOne(Jedi::name eq "Yoda") // Compile error (2 is not a String) val error = col.findOne(Jedi::name eq 2) // Use property reference with instances val yoda2 = col.findOne(yoda::name regex "Yo.*")
KMongo also supports string queries that let you construct queries with MongoDB Query Language:
import org.litote.kmongo.MongoOperator.lt import org.litote.kmongo.MongoOperator.match import org.litote.kmongo.MongoOperator.regex import org.litote.kmongo.MongoOperator.sample val yoda = col.findOne("{name: {$regex: 'Yo.*'}}")!! val luke = col.aggregate<Jedi>("""[ {$match:{age:{$lt : ${yoda.age}}}}, {$sample:{size:1}} ]""").first()
For more information, see the following KMongo documentation:
Data Typing
Both drivers support the use of Kotlin data classes as well as the Document
class to
model the data stored in a MongoDB collection. The Document
class lets you model data represented in a MongoDB collection in a flexible format.
You can use data classes and Document
classes to model data with the
MongoDB Kotlin driver:
// With data class data class Movie(val title: String, val year: Int, val rating: Float) val dataClassCollection = database.getCollection<Movie>("movies") val movieDataClass = dataClassCollection.findOneOrNull() val movieNameDataClass = movieDataClass.title // With Document class val documentCollection = database.getCollection<Movie>("movies") val movieDocument = documentCollection.findOneOrNull() val movieTitleDocument = movieDocument.getString("title")
You can use data classes and Document
classes to model data in KMongo:
// With data class data class Movie(val title: String, val year: Int, val rating: Float) val collection = database.getCollection<Movie>("movies") val movieDataClass = dataClassCollection.findOne() val movieNameDataClass = movieDataClass.title // With Document class val documentCollection = database.getCollection("movies") val movieDocument = documentCollection.findOne() val movieTitleDocument = movieDocument.getString("title")
Data Serialization
Both drivers provide support for serializing and deserializing data objects in Kotlin to and from BSON.
You can serialize data classes in the Kotlin driver using both automatic
data class codecs as well as the kotlinx.serialization
library. The
driver provides an efficient Bson
serializer that handles the
serialization of Kotlin objects to BSON data.
data class LightSaber( // Use instead of @BsonId val id: ObjectId?, val color: String, val qty: Int, val manufacturer: String = "Acme" // Use instead of @BsonProperty )
To learn more, see the Kotlin Serialization documentation.
If you use the Document
class to represent your collection, you can
serialize it to JSON and EJSON using the .toJson()
method:
val document = Document("_id", 1).append("color", "blue") // Serialize to JSON document.toJson() // Serialize to EJSON val settings = JsonWriterSettings.builder().outputMode(JsonMode.STRICT).build() val json = doc.toJson(settings)
To learn more about serializing data with the Document
class, refer to
Document Data Format - Extended JSON documentation.
You can serialize data in KMongo using the following serialization libraries:
Jackson
(default)POJO Codec engine
kotlinx.serialization
// Using KotlinX Serialization data class Data( val _id: Id<Data> = newId()) val json = Json { serializersModule = IdKotlinXSerializationModule } val data = Data() val json = json.encodeToString(data)
To learn more about the KMongo serialization methods, refer to the Object Mapping KMongo documentation.
Synchronous and Asynchronous Support
Both drivers support synchronous and asynchronous operations.
The MongoDB Kotlin driver also has separate libraries for synchronous and asynchronous operations. However, the Kotlin driver only has built-in support for coroutines as an asynchronous paradigm. The MongoDB Kotlin driver does not currently provide support for other asynchronous paradigms such as Reactive Streams, Reactor, or RxJava2.
Driver | Package |
---|---|
Sync | com.mongodb.kotlin.client |
Coroutines | com.mongodb.kotlin.client.coroutine |
Unlike KMongo, if you want to write asynchronous code, you only need to import the relevant package.
To write synchronous code:
import com.mongodb.kotlin.client.MongoClient // Instantiate your collection data class Jedi(val name: String, val age: Int) val uri = "<your-connection-string"> val mongoClient = MongoClient.create(uri) val database = mongoClient.getDatabase("test") val collection = database.getCollection<Jedi>("jedi") // Synchronous operations val jedi =a Jedi("Luke Skywalker", 19) collection.insertOne(jedi)
To write asynchronous coroutine code:
import com.mongodb.kotlin.client.coroutine.MongoClient // Instantiate your collection data class Jedi(val name: String, val age: Int) val uri = "<your-connection-string"> val mongoClient = MongoClient.create(uri) val database = mongoClient.getDatabase("test") val collection = database.getCollection<Jedi>("jedi") runBlocking { // Async operations val jedi =a Jedi("Luke Skywalker", 19) collection.insertOne(jedi) }
KMongo has a core library org.litote.kmongo:kmongo
with main functionality and
separate companion libraries that provide asynchronous support to the core library.
KMongo supports the following asynchronous paradigms:
Async Style | Package |
---|---|
Reactive Streams | org.litote.kmongo:kmongo-async |
Coroutines | com.mongodb.kotlin.client.coroutine and org.litote.kmongo.coroutine |
Reactor | org.litote.kmongo:kmongo-reactor |
RxJava2 | org.litote.kmongo:kmongo-rxjava2 |
To write synchronous code with KMongo:
import org.litote.kmongo.* // Instantiate your collection data class Jedi(val name: String, val age: Int) val client = KMongo.createClient() val database = client.getDatabase("test") val col = database.getCollection<Jedi>() // Synchronous operations col.insertOne(Jedi("Luke Skywalker", 19)) val yoda : Jedi? = col.findOne(Jedi::name eq "Yoda")
To write async coroutine code with KMongo:
import org.litote.kmongo.reactivestreams.* import org.litote.kmongo.coroutine.* // Instantiate your collection data class Jedi(val name: String, val age: Int) val client = KMongo.createClient() val database = client.getDatabase("test") val col = database.getCollection<Jedi>() runBlocking { // Async operations col.insertOne(Jedi("Luke Skywalker", 19)) val yoda : Jedi? = col.findOne(Jedi::name eq "Yoda") }
To learn more, refer to the Quick Start in the KMongo documentation.
What Next?
Now that you have learned about the differences between KMongo and the MongoDB Kotlin driver, see the Quick Start to get started using the KMongo Kotlin driver.