Explore Developer Center's New Chatbot! MongoDB AI Chatbot can be accessed at the top of your navigation to answer all your MongoDB questions.

Join us at AWS re:Invent 2024! Learn how to use MongoDB for AI use cases.
MongoDB Developer
MongoDB
plus
Sign in to follow topics
MongoDB Developer Centerchevron-right
Developer Topicschevron-right
Productschevron-right
MongoDBchevron-right

Getting Started With the MongoDB Kotlin Driver

Mohit Sharma9 min read • Published Sep 09, 2024 • Updated Sep 09, 2024
MongoDBKotlin
Facebook Icontwitter iconlinkedin icon
Rate this tutorial
star-empty
star-empty
star-empty
star-empty
star-empty
Some features mentioned below will be deprecated on Sep. 30, 2025. Learn more.
This is an introductory article on how to build an application in Kotlin using MongoDB Atlas and the MongoDB Kotlin driver, the latest addition to our list of official drivers. Together, we'll build a CRUD application that covers the basics of how to use MongoDB as a database, while leveraging the benefits of Kotlin as a programming language, like data classes, coroutines, and flow.

Prerequisites

This is a getting-started article. Therefore, not much is needed as a prerequisite, but familiarity with Kotlin as a programming language will be helpful.
Also, we need an Atlas account, which is free forever. Create an account if you haven't got one. This provides MongoDB as a cloud database and much more. Later in this tutorial, we'll use this account to create a new cluster, load a dataset, and eventually query against it.
In general, MongoDB is an open-source, cross-platform, and distributed document database that allows building apps with flexible schema. In case you are not familiar with it or would like a quick recap, I recommend exploring the MongoDB Jumpstart series to get familiar with MongoDB and its various services in under 10 minutes. Or if you prefer to read, then you can follow our guide.
And last, to aid our development activities, we will be using Jetbrains IntelliJ IDEA (Community Edition), which has default support for the Kotlin language.

MongoDB Kotlin driver vs MongoDB Realm Kotlin SDK

Before we start, I would like to touch base on Realm Kotlin SDK, one of the SDKs used to create client-side mobile applications using the MongoDB ecosystem. It shouldn't be confused with the MongoDB Kotlin driver for server-side programming. The MongoDB Kotlin driver, a language driver, enables you to seamlessly interact with Atlas, a cloud database, with the benefits of the Kotlin language paradigm. It's appropriate to create backend apps, scripts, etc.
To make learning more meaningful and practical, we'll be building a CRUD application. Feel free to check out our Github repo if you would like to follow along together. So, without further ado, let's get started.

Create a project

To create the project, we can use the project wizard, which can be found under the File menu options. Then, select New, followed by Project. This will open the New Project screen, as shown below, then update the project and language to Kotlin.
Project wizard
After the initial Gradle sync, our project is ready to run. So, let's give it a try using the run icon in the menu bar, or simply press CTRL + R on Mac. Currently, our project won't do much apart from printing Hello World! and arguments supplied, but the BUILD SUCCESSFUL message in the run console is what we're looking for, which tells us that our project setup is complete.
build success
Now, the next step is to add the Kotlin driver to our project, which allows us to interact with MongoDB Atlas.

Adding the MongoDB Kotlin driver

Adding the driver to the project is simple and straightforward. Just update the dependencies block with the Kotlin driver dependency in the build file — i.e., build.gradle.
1dependencies {
2 // Kotlin coroutine dependency
3 implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.6.4")
4
5 // MongoDB Kotlin driver dependency
6 implementation("org.mongodb:mongodb-driver-kotlin-coroutine:4.10.1")
7}
And now, we are ready to connect with MongoDB Atlas using the Kotlin driver.

Connecting to the database

To connect with the database, we first need the Connection URI that can be found by pressing connect to cluster in our Atlas account, as shown below.
image
For more details, you can also refer to our documentation.
With the connection URI available, the next step is to create a Kotlin file. Setup.kt is where we write the code for connecting to MongoDB Atlas.
Setup.kt file
Connection with our database can be split into two steps. First, we create a MongoClient instance using Connection URI.
1val connectionString = "mongodb+srv://<username>:<enter your password>@cluster0.sq3aiau.mongodb.net/?retryWrites=true&w=majority"
2val client = MongoClient.create(connectionString = connectString)
And second, use client to connect with the database, sample_restaurants, which is a sample dataset for restaurants. A sample dataset is a great way to explore the platform and build a more realistic POC to validate your ideas. To learn how to seed your first Atlas database with sample data, visit the docs.
1val databaseName = "sample_restaurants"
2val db: MongoDatabase = client.getDatabase(databaseName = databaseName)
Hardcoding connectionString isn't a good approach and can lead to security risks or an inability to provide role-based access. To avoid such issues and follow the best practices, we will be using environment variables. Other common approaches are the use of Vault, build configuration variables, and CI/CD environment variables.
To add environment variables, use Modify run configuration, which can be found by right-clicking on the file.
add environment variable
Together with code to access the environment variable, our final code looks like this.
1suspend fun setupConnection(
2 databaseName: String = "sample_restaurants",
3 connectionEnvVariable: String = "MONGODB_URI"
4): MongoDatabase? {
5 val connectString = if (System.getenv(connectionEnvVariable) != null) {
6 System.getenv(connectionEnvVariable)
7 } else {
8 "mongodb+srv://<usename>:<password>@cluster0.sq3aiau.mongodb.net/?retryWrites=true&w=majority"
9 }
10
11 val client = MongoClient.create(connectionString = connectString)
12 val database = client.getDatabase(databaseName = databaseName)
13
14 return try {
15 // Send a ping to confirm a successful connection
16 val command = Document("ping", BsonInt64(1))
17 database.runCommand(command)
18 println("Pinged your deployment. You successfully connected to MongoDB!")
19 database
20 } catch (me: MongoException) {
21 System.err.println(me)
22 null
23 }
24}
In the code snippet above, we still have the ability to use a hardcoded string. This is only done for demo purposes, allowing you to use a connection URI directly for ease and to run this via any online editor. But it is strongly recommended to avoid hardcoding a connection URI.
With the setupConnection function ready, let's test it and query the database for the collection count and name.
1suspend fun listAllCollection(database: MongoDatabase) {
2
3 val count = database.listCollectionNames().count()
4 println("Collection count $count")
5
6 print("Collection in this database are -----------> ")
7 database.listCollectionNames().collect { print(" $it") }
8}
Upon running that code, our output looks like this:
list collection output
By now, you may have noticed that we are using the suspend keyword with listAllCollection(). listCollectionNames() is an asynchronous function as it interacts with the database and therefore would ideally run on a different thread. And since the MongoDB Kotlin driver supports Coroutines, the native Kotlin asynchronous language paradigm, we can benefit from it by using suspend functions.
Similarly, to drop collections, we use the suspend function.
1suspend fun dropCollection(database: MongoDatabase) {
2 database.getCollection<Objects>(collectionName = "restaurants").drop()
3}
With this complete, we are all set to start working on our CRUD application. So to start with, we need to create a data class that represents restaurant information that our app saves into the database.
1data class Restaurant(
2 @BsonId
3 val id: ObjectId,
4 val address: Address,
5 val borough: String,
6 val cuisine: String,
7 val grades: List<Grade>,
8 val name: String,
9 @BsonProperty("restaurant_id")
10 val restaurantId: String
11)
12
13data class Address(
14 val building: String,
15 val street: String,
16 val zipcode: String,
17 val coord: List<Double>
18)
19
20data class Grade(
21 val date: LocalDateTime,
22 val grade: String,
23 val score: Int
24)
In the above code snippet, we used two annotations:
  1. @BsonId, which represents the unique identity or _id of a document.
  2. @BsonProperty, which creates an alias for keys in the document — for example, restaurantId represents restaurant_id.
Note: Our Restaurant data class here is an exact replica of a restaurant document in the sample dataset, but a few fields can be skipped or marked as optional — e.g., grades and address — while maintaining the ability to perform CRUD operations. We are able to do so, as MongoDB’s document model allows flexible schema for our data.

Create

With all the heavy lifting done (10 lines of code for connecting), adding a new document to the database is really simple and can be done with one line of code using insertOne. So, let's create a new file called Create.kt, which will contain all the create operations.
1suspend fun addItem(database: MongoDatabase) {
2
3 val collection = database.getCollection<Restaurant>(collectionName = "restaurants")
4 val item = Restaurant(
5 id = ObjectId(),
6 address = Address(
7 building = "Building", street = "street", zipcode = "zipcode", coord =
8 listOf(Random.nextDouble(), Random.nextDouble())
9 ),
10 borough = "borough",
11 cuisine = "cuisine",
12 grades = listOf(
13 Grade(
14 date = LocalDateTime.now(),
15 grade = "A",
16 score = Random.nextInt()
17 )
18 ),
19 name = "name",
20 restaurantId = "restaurantId"
21 )
22
23 collection.insertOne(item).also {
24 println("Item added with id - ${it.insertedId}")
25 }
26}
When we run it, the output on the console is:
insert one
Again, don't forget to add an environment variable again for this file, if you had trouble while running it.
If we want to add multiple documents to the collection, we can use insertMany, which is recommended over running insertOne in a loop.
1suspend fun addItems(database: MongoDatabase) {
2 val collection = database.getCollection<Restaurant>(collectionName = "restaurants")
3 val newRestaurants = collection.find<Restaurant>().first().run {
4 listOf(
5 this.copy(
6 id = ObjectId(), name = "Insert Many Restaurant first", restaurantId = Random
7 .nextInt().toString()
8 ),
9 this.copy(
10 id = ObjectId(), name = "Insert Many Restaurant second", restaurantId = Random
11 .nextInt().toString()
12 )
13 )
14 }
15
16 collection.insertMany(newRestaurants).also {
17 println("Total items added ${it.insertedIds.size}")
18 }
19}
Insert many output
With these outputs on the console, we can say that the data has been added successfully.
But what if we want to see the object in the database? One way is with a read operation, which we would do shortly or use MongoDB Compass to view the information.
MongoDB Compass is a free, interactive GUI tool for querying, optimizing, and analyzing the MongoDB data from your system. To get started, download the tool and use the connectionString to connect with the database.
MongoDB compass

Read

To read the information from the database, we can use the find operator. Let's begin by reading any document.
1val collection = database.getCollection<Restaurant>(collectionName = "restaurants")
2collection.find<Restaurant>().limit(1).collect {
3 println(it)
4}
The find operator returns a list of results, but since we are only interested in a single document, we can use the limit operator in conjunction to limit our result set. In this case, it would be a single document.
If we extend this further and want to read a specific document, we can add filter parameters over the top of it:
1val queryParams = Filters
2 .and(
3 listOf(
4 eq("cuisine", "American"),
5 eq("borough", "Queens")
6 )
7 )
Or, we can use any of the operators from our list. The final code looks like this.
1suspend fun readSpecificDocument(database: MongoDatabase) {
2 val collection = database.getCollection<Restaurant>(collectionName = "restaurants")
3 val queryParams = Filters
4 .and(
5 listOf(
6 eq("cuisine", "American"),
7 eq("borough", "Queens")
8 )
9 )
10
11
12 collection
13 .find<Restaurant>(queryParams)
14 .limit(2)
15 .collect {
16 println(it)
17 }
18
19}
For the output, we see this:
read specific doc output
Don't forget to add the environment variable again for this file, if you had trouble while running it.
Another practical use case that comes with a read operation is how to add pagination to the results. This can be done with the limit and offset operators.
1suspend fun readWithPaging(database: MongoDatabase, offset: Int, pageSize: Int) {
2 val collection = database.getCollection<Restaurant>(collectionName = "restaurants")
3 val queryParams = Filters
4 .and(
5 listOf(
6 eq(Restaurant::cuisine.name, "American"),
7 eq(Restaurant::borough.name, "Queens")
8 )
9 )
10
11 collection
12 .find<Restaurant>(queryParams)
13 .limit(pageSize)
14 .skip(offset)
15 .collect {
16 println(it)
17 }
18}
But with this approach, often, the query response time increases with value of the offset. To overcome this, we can benefit by creating an Index, as shown below.
1val collection = database.getCollection<Restaurant>(collectionName = "restaurants")
2val options = IndexOptions().apply {
3 this.name("restaurant_id_index")
4 this.background(true)
5}
6
7collection.createIndex(
8 keys = Indexes.ascending("restaurant_id"),
9 options = options
10)

Update

Now, let's discuss how to edit/update an existing document. Again, let's quickly create a new Kotlin file, Update.Kt.
In general, there are two ways of updating any document:
  • Perform an update operation, which allows us to update specific fields of the matching documents without impacting the other fields.
  • Perform a replace operation to replace the matching document with the new document.
For this exercise, we'll use the document we created earlier with the create operation {restaurant_id: "restaurantId"} and update the restaurant_id with a more realistic value. Let's split this into two sub-tasks for clarity.
First, using Filters, we query to filter the document, similar to the read operation earlier.
1val collection = db.getCollection<Restaurant>("restaurants")
2val queryParam = Filters.eq("restaurant_id", "restaurantId")
Then, we can set the restaurant_id with a random integer value using Updates.
1val updateParams = Updates.set("restaurant_id", Random.nextInt().toString())
And finally, we use updateOne to update the document in an atomic operation.
1collection.updateOne(filter = queryParam, update = updateParams).also {
2 println("Total docs modified ${it.matchedCount} and fields modified ${it.modifiedCount}")
3}
In the above example, we were already aware of which document we wanted to update — the restaurant with an id restauratantId — but there could be a few use cases where that might not be the situation. In such cases, we would first look up the document and then update it. findOneAndUpdate can be handy. It allows you to combine both of these processes into an atomic operation, unlocking additional performance.
Another variation of the same could be updating multiple documents with one call. updateMany is useful for such use cases — for example, if we want to update the cuisine of all restaurants to your favourite type of cuisine and borough to Brooklyn.
1suspend fun updateMultipleDocuments(db: MongoDatabase) {
2 val collection = db.getCollection<Restaurant>("restaurants")
3 val queryParam = Filters.eq(Restaurant::cuisine.name, "Chinese")
4 val updateParams = Updates.combine(
5 Updates.set(Restaurant::cuisine.name, "Indian"),
6 Updates.set(Restaurant::borough.name, "Brooklyn")
7 )
8
9 collection.updateMany(filter = queryParam, update = updateParams).also {
10 println("Total docs matched ${it.matchedCount} and modified ${it.modifiedCount}")
11 }
12}
In these examples, we used set and combine with Updates. But there are many more types of update operator to explore that allow us to do many intuitive operations, like set the currentDate or timestamp, increase or decrease the value of the field, and so on. To learn more about the different types of update operators you can perform with Kotlin and MongoDB, refer to our docs.

Delete

Now, let's explore one final CRUD operation: delete. We'll start by exploring how to delete a single document. To do this, we'll use findOneAndDelete instead of deleteOne. As an added benefit, this also returns the deleted document as output. In our example, we delete the restaurant:
1val collection = db.getCollection<Restaurant>(collectionName = "restaurants")
2val queryParams = Filters.eq("restaurant_id", "restaurantId")
3
4collection.findOneAndDelete(filter = queryParams).also {
5 it?.let {
6 println(it)
7 }
8}
delete output
To delete multiple documents, we can use deleteMany. We can, for example, use this to delete all the data we created earlier with our create operation.
1suspend fun deleteRestaurants(db: MongoDatabase) {
2 val collection = db.getCollection<Restaurant>(collectionName = "restaurants")
3
4 val queryParams = Filters.or(
5 listOf(
6 Filters.regex(Restaurant::name.name, Pattern.compile("^Insert")),
7 Filters.regex("restaurant_id", Pattern.compile("^restaurant"))
8 )
9 )
10 collection.deleteMany(filter = queryParams).also {
11 println("Document deleted : ${it.deletedCount}")
12 }
13}

Summary

Congratulations! You now know how to set up your first Kotlin application with MongoDB and perform CRUD operations. The complete source code of the app can be found on GitHub.
If you have any feedback on your experience working with the MongoDB Kotlin driver, please submit a comment in our user feedback portal or reach out to me on Twitter: @codeWithMohit.

Facebook Icontwitter iconlinkedin icon
Rate this tutorial
star-empty
star-empty
star-empty
star-empty
star-empty
Related
Tutorial

The 5-Minute Guide to Working with ESG Data on MongoDB


Aug 24, 2023 | 11 min read
Quickstart

Introduction to MongoDB and Helidon


Nov 12, 2024 | 6 min read
Tutorial

Real-time Data Architectures With MongoDB Cloud Manager and Verizon 5G Edge


Aug 28, 2024 | 8 min read
Article

Bloated Documents


Oct 01, 2024 | 6 min read
Table of Contents