---
title: Notes on Accessing MongoDB-Compatible FerretDB with Spring Boot + Testcontainers
tags: ["Spring Boot", "FerretDB", "MongoDB", "Testcontainers", "DocumentDB", "Java"]
categories: ["Programming", "Java", "org", "springframework", "data", "mongodb"]
date: 2025-07-18T03:43:12Z
updated: 2025-07-18T03:49:24Z
---

> ⚠️ This article was automatically translated by OpenAI (gpt-4.1).
> It may be edited eventually, but please be aware that it may contain incorrect information at this time.

[FerretDB](https://docs.ferretdb.io/) is an open-source database compatible with MongoDB, using [DocumentDB](https://github.com/microsoft/documentdb) extensions and PostgreSQL as its backend.
While MongoDB has switched to the SSPL (Server Side Public License), FerretDB is licensed under Apache 2.0 (the DocumentDB extension is under the MIT license).

> [!TIP]
> By the way, Tanzu for PostgreSQL also supports FerretDB.
> https://techdocs.broadcom.com/us/en/vmware-tanzu/data-solutions/tanzu-for-postgres/17-5/tnz-postgres/index.html

In this post, I'll jot down how to access FerretDB using Spring Boot + Spring Data MongoDB.
From the application's perspective, it's the same as MongoDB; only the configuration for Testcontainers changes.

### Creating a Project Template

Use Spring Initializr to create a Spring Boot project template.

```bash
curl -s https://start.spring.io/starter.tgz \
       -d artifactId=demo-ferretdb \
       -d name=demo-ferretdb \
       -d baseDir=demo-ferretdb  \
       -d packageName=com.example \
       -d dependencies=web,data-mongodb,actuator,configuration-processor,prometheus,native,testcontainers \
       -d type=maven-project \
       -d applicationName=DemoFerretApplication | tar -xzvf -
cd demo-ferretdb 
```

### Creating a Sample App

Let's create a very simple application to save and retrieve messages.

```java
cat <<EOF > src/main/java/com/example/Message.java
package com.example;

public record Message(String id, String text) {
}
EOF
```

```java
cat <<EOF > src/main/java/com/example/HelloController.java
package com.example;

import java.util.List;
import org.springframework.data.mongodb.core.MongoTemplate;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class HelloController {

	private final MongoTemplate mongoTemplate;

	public HelloController(MongoTemplate mongoTemplate) {
		this.mongoTemplate = mongoTemplate;
	}

	@PostMapping(path = "/messages")
	public Message postMessage(@RequestBody String text) {
		return mongoTemplate.save(new Message(null, text));
	}

	@GetMapping(path = "/messages")
	public List<Message> getMessages() {
		return mongoTemplate.findAll(Message.class);
	}

}
EOF
```

Since Testcontainers was added via Spring Initializr, the following file is included in the project.

```java
package com.example;

import org.springframework.boot.test.context.TestConfiguration;
import org.springframework.boot.testcontainers.service.connection.ServiceConnection;
import org.springframework.context.annotation.Bean;
import org.testcontainers.containers.MongoDBContainer;
import org.testcontainers.utility.DockerImageName;

@TestConfiguration(proxyBeanMethods = false)
class TestcontainersConfiguration {

	@Bean
	@ServiceConnection
	MongoDBContainer mongoDbContainer() {
		return new MongoDBContainer(DockerImageName.parse("mongo:latest"));
	}

}
```

Let's start the app using Testcontainers. Either run `src/test/java/com/example/TestDemoFerretApplication.java` or execute the following command:

```bash
./mvnw spring-boot:test-run
```

Once the application and MongoDB are running, try POSTing & GETting messages as follows.

```bash
$ curl http://localhost:8080/messages -H content-type:text/plain -d "Hello MongoDB\!"
{"id":"6879a6f3ba99e4ec5c9419fd","text":"Hello MongoDB!"}

$ curl http://localhost:8080/messages -H content-type:text/plain -d "Hello FerretDB\!"
{"id":"6879a6f8ba99e4ec5c9419fe","text":"Hello FerretDB!"}

$ curl -s http://localhost:8080/messages | jq .
[
  {
    "id": "6879a6f3ba99e4ec5c9419fd",
    "text": "Hello MongoDB!"
  },
  {
    "id": "6879a6f8ba99e4ec5c9419fe",
    "text": "Hello FerretDB!"
  }
]
```

The test code corresponding to this operation check is as follows.

```java
package com.example;

import java.util.List;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.web.server.LocalServerPort;
import org.springframework.context.annotation.Import;
import org.springframework.core.ParameterizedTypeReference;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.web.client.RestClient;

import static org.assertj.core.api.Assertions.assertThat;

@Import(TestcontainersConfiguration.class)
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
class DemoFerretApplicationTests {

	RestClient restClient;

	@BeforeEach
	void setUp(@LocalServerPort int port, @Autowired RestClient.Builder restClientBuilder) {
		this.restClient = restClientBuilder.defaultStatusHandler(statusCode -> true, (req, res) -> {
			/* NO-OP */}).baseUrl("http://localhost:" + port).build();
	}

	@Test
	void contextLoads() {
		{
			ResponseEntity<Message> res = this.restClient.post()
				.uri("/messages")
				.contentType(MediaType.TEXT_PLAIN)
				.body("Hello MongoDB!")
				.retrieve()
				.toEntity(Message.class);
			assertThat(res.getStatusCode()).isEqualTo(HttpStatus.OK);
			Message message = res.getBody();
			assertThat(message).isNotNull();
			assertThat(message.text()).isEqualTo("Hello MongoDB!");
			assertThat(message.id()).isNotNull();
		}
		{
			ResponseEntity<Message> res = this.restClient.post()
				.uri("/messages")
				.contentType(MediaType.TEXT_PLAIN)
				.body("Hello FerretDB!")
				.retrieve()
				.toEntity(Message.class);
			assertThat(res.getStatusCode()).isEqualTo(HttpStatus.OK);
			Message message = res.getBody();
			assertThat(message).isNotNull();
			assertThat(message.text()).isEqualTo("Hello FerretDB!");
			assertThat(message.id()).isNotNull();
		}
		{
			ResponseEntity<List<Message>> res = this.restClient.get()
				.uri("/messages")
				.retrieve()
				.toEntity(new ParameterizedTypeReference<>() {
				});
			assertThat(res.getStatusCode()).isEqualTo(HttpStatus.OK);
			List<Message> messages = res.getBody();
			assertThat(messages).isNotNull();
			assertThat(messages).hasSize(2);
			assertThat(messages).map(Message::id).allSatisfy(id -> assertThat(id).isNotNull());
			assertThat(messages).map(Message::text).containsExactly("Hello MongoDB!", "Hello FerretDB!");
		}
	}

}
```

MongoDB will also start via Testcontainers during testing. You can run the tests with the following command:

```bash
./mvnw clean test
```

### Switching to FerretDB

Let's switch from MongoDB to FerretDB. You don't need to change the application code; only the Testcontainers configuration needs to be updated.
Since the FerretDB container image is not compatible with `MongoDBContainer`, use `GenericContainer` instead.
Therefore, don't use ServiceConnection, but use `DynamicPropertyRegistrar` to dynamically register MongoDB connection info. Since FerretDB has authentication enabled by default, set `spring.data.mongodb.uri` including the username and password.

Change `TestcontainersConfiguration` as follows:

```java
cat <<EOF > src/test/java/com/example/TestcontainersConfiguration.java
package com.example;

import org.springframework.boot.test.context.TestConfiguration;
import org.springframework.context.annotation.Bean;
import org.springframework.test.context.DynamicPropertyRegistrar;
import org.testcontainers.containers.GenericContainer;
import org.testcontainers.containers.wait.strategy.HostPortWaitStrategy;
import org.testcontainers.utility.DockerImageName;

@TestConfiguration(proxyBeanMethods = false)
class TestcontainersConfiguration {

	@Bean
	GenericContainer<?> ferretDbContainer() {
		return new GenericContainer<>(DockerImageName.parse("ghcr.io/ferretdb/ferretdb-eval:2"))
			.withExposedPorts(27017, 5432)
			.withEnv("POSTGRES_USER", "user")
			.withEnv("POSTGRES_PASSWORD", "password")
			.withEnv("FERRETDB_TELEMETRY", "false")
			.waitingFor(new HostPortWaitStrategy().forPorts(27017, 5432));
	}

	@Bean
	DynamicPropertyRegistrar dynamicPropertyRegistrar(GenericContainer<?> ferretDbContainer) {
		return registry -> registry.add("spring.data.mongodb.uri", () -> "mongodb://user:password@%s:%d/test"
			.formatted(ferretDbContainer.getHost(), ferretDbContainer.getMappedPort(27017)));
	}

}
EOF
```

The same tests should pass after changing `TestcontainersConfiguration`:

```bash
./mvnw clean test
```

Either rerun `src/test/java/com/example/TestDemoFerretApplication.java` or execute the following command again:

```bash
./mvnw spring-boot:test-run
```

As before, try POSTing & GETting messages.

```bash
$ curl http://localhost:8080/messages -H content-type:text/plain -d "Hello MongoDB\!"
{"id":"6879af654c503243968ecba0","text":"Hello MongoDB!"}

$ curl http://localhost:8080/messages -H content-type:text/plain -d "Hello FerretDB\!"
{"id":"6879af6a4c503243968ecba1","text":"Hello FerretDB!"}

$ curl -s http://localhost:8080/messages | jq .
[
  {
    "id": "6879af654c503243968ecba0",
    "text": "Hello MongoDB!"
  },
  {
    "id": "6879af6a4c503243968ecba1",
    "text": "Hello FerretDB!"
  }
]
```

You can confirm that switching from MongoDB to FerretDB works without any issues.

### Running Standalone

Let's try running the application standalone, without Testcontainers.

Start FerretDB with the following `docker run` command:

```bash
docker run --rm --name ferretdb -p 27017:27017 -e POSTGRES_USER=user -e POSTGRES_PASSWORD=password -e FERRETDB_TELEMETRY=false ghcr.io/ferretdb/ferretdb-eval:2
```

Create an executable jar file with the following command:

```bash
./mvnw clean package
```

Specify `spring.data.mongodb.uri` at runtime to connect to FerretDB.

```bash
java -jar target/demo-ferretdb-0.0.1-SNAPSHOT.jar --spring.data.mongodb.uri=mongodb://user:password@localhost:27017/test
```

You should be able to POST & GET messages as before.

The source code used for this operation check is [here](https://github.com/making/demo-ferretdb).

---

This post introduced how to use FerretDB with Spring Boot + Spring Data MongoDB + Testcontainers.
By using FerretDB as an alternative to SSPL-licensed MongoDB, you can avoid licensing issues while still using MongoDB-compatible features.

For detailed compatibility, please refer to the [documentation](https://docs.ferretdb.io/migration/compatibility/).
