In this post, we’ll learn how to manage and apply MongoDB database schema changes using Mongock.
· Prerequisites
· Overview
∘ What is Mongock?
∘ Why Mongock?
∘ Mongock Architecture
· Let’s get to the code
∘ Add Dependencies
∘ Enable Mongock
∘ Create Migration Classes
· Test the application
· Conclusion
· References
Prerequisites
This is the list of all the prerequisites:
- Spring Boot 3+
- Maven 3.6.3
- Java 21
- MongoDB instance (v6 or later) installed
Overview
In one of my projects, I was looking for simple options to manage MongoDB database schema changes, similar to those in other projects like Flyway or Liquibase. I found Mongock, an open-source Java MongoDB tool.
What is Mongock?
Mongock is a Java-based migration tool specifically designed to manage database schema changes in MongoDB and other NoSQL databases. It provides a systematic approach to handle database migrations, ensuring that code and data changes are executed together during application deployment.
Why Mongock?
- It’s open-source and free to use (under the Apache License 2.0).
- Great support with the Spring Framework overall, providing native implementations in SpringBoot.
- Allows for easy rollback of changes if something goes wrong during the migration process.
- Adopted by well-known frameworks such as JHipster as part of the scaffolding.
- Helps maintain consistency across different environments (development, testing, production) by applying the same migrations.
Mongock Architecture
Mongock process follows the next steps:
- The runner loads the migration files(changeUnits).
- The runner checks if there is a pending change to execute.
- The runner acquires the distributed lock through the driver.
- The runner loops over the migration files(changeUnits) in order.
- Takes the next ChangeUnit and executes it.
- If the ChangeUnit is successfully executed, Mongock persists an entry in the Mongock change history with the state SUCCESS and start the step 5 again.
- If the ChangeUnit fails, the runner rolls back the change. If the driver supports transactions and transactions are enabled, the rollback is done natively. When the driver does not support transactions or transactions are disabled, the method @RollbackExecution is executed. In both cases the ChangeUnit failed, whereas in the latter option, and entry is added in the changelog that a change has been rolled back.
6. If the runner acomplished to execute the entire migration with no failures, it’s considered successful. It releases the lock and finishes the migration.
7. On the other hand, if any ChangeUnit fails, the runner stops the migration at that point and throws an exception. When Mongock is executed again, it will continue from the failure ChangeUnit(included).
Let’s get to the code
We’ll start by creating a simple Spring Boot project from start.spring.io.

Add Dependencies
To start using Mongock in our project, we need to add the dependencies below in the pom.xml file.
<dependencyManagement>
<dependencies>
<dependency>
<groupId>io.mongock</groupId>
<artifactId>mongock-bom</artifactId>
<version>5.5.0</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
- Add the runner Spring Boot dependency
<dependency>
<groupId>io.mongock</groupId>
<artifactId>mongock-springboot</artifactId>
</dependency>
- Add the MongoDB Spring Data driver dependency
<dependency>
<groupId>io.mongock</groupId>
<artifactId>mongodb-springdata-v4-driver</artifactId>
</dependency>
Enable Mongock
Once you have successfully imported the necessary dependencies, there are two ways to activate Mongock in our project. In most cases, when using the Spring framework, the easiest and most convenient way is the annotation approach. However, sometimes you don’t use Spring or you need more control over your Mongock bean. In this case, you should go for the traditional builder approach.
We opt for the annotation approach in our project.
- Add the
@EnableMongockannotation to the main class.
@SpringBootApplication
@EnableMongock
public class SpringMongoMigrationApplication {
public static void main(String[] args) {
SpringApplication.run(SpringMongoMigrationApplication.class, args);
}
}
- In the
application.ymlfile, specify the package where the migration classes are located:
mongock:
migration-scan-package:
- com.sample.springmongomigration.config.dbmigrations
It’s an array, so you can add more than one.
Create Migration Classes
Before creating the migration classes, we will create the first models of the project that represent the collections in MongoDB. We have an Employeemodel class in which the Department class is embedded.
@Builder
@Data
@AllArgsConstructor
@NoArgsConstructor
@Document(collection = "department")
public class Department {
@Id
private String id;
@NotNull
private String code;
private String name;
}
@Builder
@Data
@AllArgsConstructor
@NoArgsConstructor
@Document(collection = "employee")
public class Employee {
@Id
private String id;
private String firstName;
private String lastName;
private String email;
@NotNull
private Department department;
}
Every class marked as @ChangeUnit will be marked as a migration class and can contain methods annotated as follows:
- @Execution: The main migration method(Mandatory).
- @RollbackExecution: This method basically reverts the changes made by the @Execution method. It’s mandatory and highly recommended to properly implement it. It can be left empty if developers don’t think is required in some scenarios.
It will be triggered in the two following situations:
When the @Execution method fails in a non-transactional environment.
In recovery operation like undo. - @BeforeExecution: Optional method that will be executed before the actual migration, meaning that it won’t be part of the transactional and executed in a non-transactional context. It’s useful to perform DDL operations in the database where they are not allowed inside a transaction, like MongoDB, or as preparation for the actual migration.
This method is treated and tracked in the database history like the @Execution method, meaning this that in case of failure, it will force the migration to be aborted, tracked in the database as failed and Mongock will run it again in the next execution.
- @RollbackBeforeExecution: Similar to the
@RollbackExecutionfor the@Executionmethod. It reverts back to the changes made by the@BeforeExecutionmethod. It’s only mandatory when the method@BeforeExecutionis present.
Let’s create our first ChangeUnit methods to insert basic data into collections.
@ChangeUnit(id = "DB-init", order = "1", author = "bootlabs")
public class DatabaseInitChangeLog {
private final MongoTemplate template;
public DatabaseInitChangeLog(MongoTemplate template) {
this.template = template;
}
@Execution
public void execute() {
// insert departments
template.insertAll(initDepartments());
// insert employees
List<Employee> employees = Stream.concat(initHrEmployees().stream(), initRadEmployees().stream()).toList();
template.insertAll(employees);
}
@RollbackExecution
public void rollback() {
template.remove(new Department());
template.remove(new Employee());
}
private List<Department> initDepartments() {
return Arrays.asList(
Department.builder()
.name("Human Resource Management")
.code("HR")
.build(),
Department.builder()
.code("R&D")
.name("Research and Development (often abbreviated to R&D)")
.build(),
Department.builder()
.name("Prod")
.code("Production")
.build()
);
}
private List<Employee> initHrEmployees() {
List<Employee> hrEmployees = new ArrayList<>();
Optional<Department> hrDepartment = initDepartments().stream().filter(department -> department.getCode().equals("HR")).findFirst();
if (hrDepartment.isPresent()) {
hrEmployees.add(Employee.builder()
.firstName("Dioms")
.lastName("Kane")
.email("diom.kan@yahoo.fr")
.department(hrDepartment.get())
.build());
hrEmployees.add(Employee.builder()
.firstName("Astrid")
.lastName("Flob")
.email("f.astrid@demo.com")
.department(hrDepartment.get())
.build());
}
return hrEmployees;
}
private List<Employee> initRadEmployees() {
List<Employee> radEmployees = new ArrayList<>();
Optional<Department> radDepartment = initDepartments().stream().filter(department -> department.getCode().equals("R&D")).findFirst();
radDepartment.ifPresent(department -> radEmployees.add(Employee.builder()
.firstName("Luis")
.lastName("Siruis")
.email("siruis.luis@test.com")
.department(department)
.build()));
return radEmployees;
}
}
Test the application
Now we can run our application.

Mongock will automatically execute the migration scripts defined in your migration classes. It keeps track of executed migrations, ensuring they are not run again on subsequent application starts in the mongockChangeLog collection. This collection is essential for ensuring that migrations are applied only once and in the correct order. It helps Mongock manage the state of your database migrations effectively. By default, the collection is named mongockChangeLog. You can customize this name if needed.

Suppose we want to add a new “salary” field to the Employee collection. It is important to update the data of existing documents.
After adding the field in Employee.java, create the ChangeUnit class.
@ChangeUnit(id = "DB-update", order = "2", author = "bootlabs")
public class DatabaseUpdateChangeLog {
private final MongoTemplate template;
public DatabaseUpdateChangeLog(MongoTemplate template) {
this.template = template;
}
@Execution
public void updateEmployees() {
template.findAll(Employee.class).forEach(employee -> {
employee.setSalary(100.0);
template.save(employee);
});
}
@RollbackExecution
public void rollback() {
template.findAll(Employee.class).forEach(employee -> {
employee.setSalary(0.0);
template.save(employee);
});
}
}


Conclusion
Well done !!. In this post, we learned how to manage and apply MongoDB database schema changes using Mongock.
The complete source code is available on GitHub.
This post is an improved version of the original version that I published on https://dzone.com
You can reach out to me and follow me on Medium, Twitter, GitHub, Linkedln
Support me through GitHub Sponsors.
Thank you for reading!! See you in the next story.
