---
title: Note on Accessing MongoDB-compatible DocumentDB with Spring Boot + Testcontainers
tags: ["Spring Boot", "DocumentDB", "MongoDB", "Testcontainers", "Java", "FerretDB"]
categories: ["Programming", "Java", "org", "springframework", "data", "mongodb"]
date: 2025-09-03T04:31:45Z
updated: 2025-09-03T04:31:45Z
---

In [the previous article](/entries/862/en), I introduced [FerretDB](https://docs.ferretdb.io/), an OSS database compatible with MongoDB.
However, [DocumentDB](https://github.com/documentdb/documentdb) itself, which is used as the backend for Documentdb, now supports the MongoDB API, making FerretDB unnecessary.

In this article, I'll memo how to access DocumentDB using Spring Boot + Spring Data MongoDB.
From an application perspective, it's the same as MongoDB - only the Testcontainers configuration changes.

The following is basically a rework of the previous article.

### Creating Project Template

Use Spring Initializr to create a Spring Boot project template.

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

### Creating Sample Application

Create a very simple application that saves and retrieves 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 we added Testcontainers with 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. Run `src/test/java/com/example/TestDemoDocumentDbApplication.java` or execute the following command:

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

Once the application and MongoDB are started, 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 DocumentDB\!"
{"id":"6879a6f8ba99e4ec5c9419fe","text":"Hello DocumentDB!"}

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

The test code equivalent to this operation verification would be as follows:

```java
cat <<EOF > src/test/java/com/example/DemoDocumentDbApplicationTests.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 DemoDocumentDbApplicationTests {

	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 DocumentDB!")
				.retrieve()
				.toEntity(Message.class);
			assertThat(res.getStatusCode()).isEqualTo(HttpStatus.OK);
			Message message = res.getBody();
			assertThat(message).isNotNull();
			assertThat(message.text()).isEqualTo("Hello DocumentDB!");
			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 DocumentDB!");
		}
	}

}
EOF
```

The test also starts MongoDB with Testcontainers. You can run the test with the following command:

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

### Switching to DocumentDB

Switch from MongoDB to DocumentDB. The application code remains the same, only the Testcontainers configuration needs to be changed.
Since the Documentdb container image was not compatible with `MongoDBContainer`, we use `GenericContainer`.
Therefore, instead of using ServiceConnection, we use `DynamicPropertyRegistrar` to dynamically register MongoDB connection information. Since Documentdb has authentication enabled by default, we set `spring.data.mongodb.uri` including 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<?> documentdbContainer() {
		return new GenericContainer<>(DockerImageName.parse("ghcr.io/microsoft/documentdb/documentdb-local:latest"))
			.withExposedPorts(10260 /* MongoDB Port */, 9712 /* PostgreSQL port */)
			.withEnv("USERNAME", "user")
			.withEnv("PASSWORD", "password")
			.withEnv("ENFORCE_SSL", "false")
			.waitingFor(new HostPortWaitStrategy().forPorts(10260, 9712));
	}

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

}
EOF
```

The same test should pass after changing `TestcontainersConfiguration`:

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

Re-run `src/test/java/com/example/TestDemoDocumentDbApplication.java` or re-execute the following command:

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

Try POSTing & GETting messages as before:

```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 DocumentDB\!"
{"id":"6879af6a4c503243968ecba1","text":"Hello DocumentDB!"}

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

We confirmed that switching from MongoDB to Documentdb works without any issues.

### Running Standalone

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

Start Documentdb with the following `docker run` command:

```bash
docker run --rm --name documentdb -p 10260:10260 -e USERNAME=user -e PASSWORD=password -e ENFORCE_SSL=false ghcr.io/microsoft/documentdb/documentdb-local:latest
```

Create an executable jar file with the following command:

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

Specify `spring.data.mongodb.uri` at runtime to connect to Documentdb:

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

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

### Enabling TLS Connection to DocumentDB

The Docker image `ghcr.io/microsoft/documentdb/documentdb-local` has TLS enabled by default.
In the previous example, we disabled TLS with the environment variable `ENFORCE_SSL=false`, but removing this setting requires TLS connection.

To enable TLS connection to MongoDB during testing, you need to register the CA certificate of the TLS certificate configured in DocumentDB to the TrustStore.
This time, we'll use [netty-pkitesting](https://central.sonatype.com/artifact/io.netty/netty-pkitesting) to create a self-signed certificate for testing and dynamically configure to use this certificate.

Add the following dependency to `pom.xml`:

```xml
    <dependency>
      <groupId>io.netty</groupId>
      <artifactId>netty-pkitesting</artifactId>
      <version>4.2.4.Final</version>
      <scope>test</scope>
    </dependency>
```

Change `TestcontainersConfiguration` as follows:

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

import io.netty.pkitesting.CertificateBuilder;
import io.netty.pkitesting.X509Bundle;
import java.io.File;
import org.slf4j.LoggerFactory;
import org.springframework.boot.ssl.DefaultSslBundleRegistry;
import org.springframework.boot.ssl.SslBundle;
import org.springframework.boot.ssl.pem.PemSslStoreBundle;
import org.springframework.boot.ssl.pem.PemSslStoreDetails;
import org.springframework.boot.test.context.TestConfiguration;
import org.springframework.context.annotation.Bean;
import org.springframework.test.context.DynamicPropertyRegistrar;
import org.testcontainers.containers.BindMode;
import org.testcontainers.containers.GenericContainer;
import org.testcontainers.containers.output.Slf4jLogConsumer;
import org.testcontainers.containers.wait.strategy.HostPortWaitStrategy;
import org.testcontainers.utility.DockerImageName;

@TestConfiguration(proxyBeanMethods = false)
class TestcontainersConfiguration {

	@Bean
	X509Bundle selfSignedCertificate() throws Exception {
		return new CertificateBuilder().subject("CN=localhost").setIsCertificateAuthority(true).buildSelfSigned();
	}

	@Bean
	GenericContainer<?> documentdbContainer(X509Bundle selfSignedCertificate) throws Exception {
		File tempCertChainPem = selfSignedCertificate.toTempCertChainPem();
		File tempPrivateKeyPem = selfSignedCertificate.toTempPrivateKeyPem();
		return new GenericContainer<>(DockerImageName.parse("ghcr.io/microsoft/documentdb/documentdb-local:latest"))
			.withExposedPorts(10260, 9712)
			.withLogConsumer(new Slf4jLogConsumer(LoggerFactory.getLogger("documentdb")))
			.waitingFor(new HostPortWaitStrategy().forPorts(10260, 9712))
			.withFileSystemBind(tempCertChainPem.getAbsolutePath(), "/tmp/cert.pem", BindMode.READ_ONLY)
			.withFileSystemBind(tempPrivateKeyPem.getAbsolutePath(), "/tmp/private.key", BindMode.READ_ONLY)
			.withEnv("CERT_PATH", "/tmp/cert.pem")
			.withEnv("KEY_FILE", "/tmp/private.key")
			.withEnv("USERNAME", "user")
			.withEnv("PASSWORD", "password");
	}

	@Bean
	DefaultSslBundleRegistry sslBundles(X509Bundle selfSignedCertificate) {
		DefaultSslBundleRegistry bundles = new DefaultSslBundleRegistry();
		bundles.registerBundle("self-signed", SslBundle.of(new PemSslStoreBundle(null,
				PemSslStoreDetails.forCertificate(selfSignedCertificate.getRootCertificatePEM()))));
		return bundles;
	}

	@Bean
	DynamicPropertyRegistrar dynamicPropertyRegistrar(GenericContainer<?> documentdbContainer) {
		return registry -> {
			registry.add("spring.data.mongodb.uri", () -> "mongodb://user:password@%s:%d/test"
				.formatted(documentdbContainer.getHost(), documentdbContainer.getMappedPort(10260)));
			registry.add("spring.data.mongodb.ssl.enabled", () -> "true");
			registry.add("spring.data.mongodb.ssl.bundle", () -> "self-signed");
		};
	}

}
EOF
```

Confirm that the test succeeds even with this configuration change:

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


As before, let's try running the application standalone without using Testcontainers.

Create a self-signed certificate with the following command:

```bash
DIR=/tmp/self-signed
mkdir -p ${DIR}

# Create CA certificate
openssl req -new -nodes -out ${DIR}/ca.csr -keyout ${DIR}/ca.key -subj "/CN=@making/O=LOL.MAKI/C=JP"
chmod og-rwx ${DIR}/ca.key

cat <<EOF > ${DIR}/ext_ca.txt
basicConstraints=CA:TRUE
keyUsage=digitalSignature,keyCertSign
EOF

openssl x509 -req -in ${DIR}/ca.csr -days 3650 -signkey ${DIR}/ca.key -out ${DIR}/ca.crt -extfile ${DIR}/ext_ca.txt

cat <<EOF > ${DIR}/ext.txt
basicConstraints=CA:FALSE
keyUsage=digitalSignature,dataEncipherment,keyEncipherment,keyAgreement
extendedKeyUsage=serverAuth,clientAuth
EOF

# Create Server certificate signed by CA
openssl req -new -nodes -out ${DIR}/server.csr -keyout ${DIR}/server.key -subj "/CN=localhost"
chmod og-rwx ${DIR}/server.key
openssl x509 -req -in ${DIR}/server.csr -days 3650 -CA ${DIR}/ca.crt -CAkey ${DIR}/ca.key -CAcreateserial -out ${DIR}/server.crt -extfile ${DIR}/ext.txt
```

Start Documentdb with the following `docker run` command:

```bash
docker run --rm --name documentdb -p 10260:10260 -v /tmp/self-signed:/tmp/self-signed -e USERNAME=user -e PASSWORD=password -e CERT_PATH=/tmp/self-signed/server.crt -e KEY_FILE=/tmp/self-signed/server.key ghcr.io/microsoft/documentdb/documentdb-local:latest
```

Create an executable jar file with the following command:

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

Specify `spring.data.mongodb.uri` at runtime to connect to Documentdb:

```bash
java -jar target/demo-documentdb-0.0.1-SNAPSHOT.jar --spring.data.mongodb.uri=mongodb://user:password@localhost:10260/test --spring.ssl.bundle.pem.self-signed.truststore.certificate=file:/tmp/self-signed/ca.crt --spring.data.mongodb.ssl.enabled=true --spring.data.mongodb.ssl.bundle=self-signed
```

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



The verified source code is available [here](https://github.com/making/demo-documentdb).


---

I introduced how to use Documentdb with Spring Boot + Spring Data MongoDB + Testcontainers.
By using DocumentDB as an alternative to SSPL MongoDB, it seems possible to avoid license issues while utilizing MongoDB-compatible features.

For other information, please refer to the [documentation](https://documentdb.io/).
