Dieser Artikel ist auch auf Deutsch verfügbar
Last year in November, the most recent major release of Spring Boot, version 3.0, appeared after five years. This means that the free support for the last still supported branch of Spring Boot 2, namely 2.7, runs out in November of this year. At the same time, the free support for version 3.0 is also coming to an end, making the most recently released version 3.1 the only version with free support.
The key aspects of Spring Boot 3.0 were the support for newer Java versions (at least Java 17 must now be used) and the updating of Java EE to Jakarta EE and the associated changes to the package names. A great many other dependencies were also updated. In particular, the upgrade to Spring Security 6 generally requires a number of changes, for which the Migration Guide can be quite helpful. Of course, there were also many other small changes and improvements to existing functionality.
In particular, Spring Boot 3.1 contains better and more direct integration of Testcontainers (see also my last column). The new support for Docker Compose during local development is also worth noting.
We would therefore like to take a detailed look at both of these new features in this column.
Testing with Testcontainers
At the end of my last column, I demonstrated a custom test slice for testing repositories based on PostgreSQL against a database started with Testcontainers. In reality, however, a simpler method would be used in such a case. This requires the two dependencies seen in Listing 1, including the BOM for Testcontainers.
We can then use the Testcontainers JUnit 5 extension in combination with the
@DynamicPropertySource provided by Spring to start a Testcontainer and declare it in the Spring
ApplicationContext (see Listing 2).
Since Testcontainers is now also part of the automatic dependency management in Spring Boot 3.1, we no longer have to import the BOM ourselves because this already takes place within
spring-boot-dependencies and we either import this BOM indirectly via
spring-boot-starter-parent or do it explicitly ourselves.
In addition to the two dependencies in Listing 1, we can now also add the dependency seen in Listing 3. This makes it possible to use the new annotation
@ServiceConnection and thereby dispense with a manual registration via
@DynamicPropertySource (see Listing 4).
This annotation ensures that a Spring bean of type
ConnectionDetails is created. With Spring Boot 3.1, beans of this type – or, more precisely, of the provided subtypes – are used for configuring the connection to external services. For databases connected via JDBC, this means that a bean of the type
JdbcConnectionDetails is used. If we don’t register a bean of this type ourselves either directly or via
@ServiceConnection on a Testcontainer, then it will be created with the properties specified under
To identify the exact type when using
@ServiceConnection on a Testcontainer, the type of the Testcontainer is generally used. The
JdbcConnectionDetails mentioned above are registered for all containers that are of type
JdbcDatabaseContainer. For other types of containers, such as for Redis, the name of the service is evaluated to determine which
ConnectionDetails must be provided. If the name is not explicitly specified via the attribute
name in the annotation, the name of the Docker image is analyzed.
For test slices utilizing the
@AutoConfigureTestDatabase for which we want to use a connection registered with
@ServiceConnection, we must manually set the attribute
NONE. If we don’t do this, the test slice will use an in-memory database. There may be improvements here in the future, such as via Ticket 19038, which would allow us to dispense with explicitly overwriting the defaults in this way.
Local development with Testcontainers
In addition to their use in tests, it is now also possible to utilize Testcontainers during development. To do this, we need to have within our test class path a class that can be started via the
main method (see Listing 5).
By convention, this should be located in the same package as the application class and have the same name plus the prefix
Test. Inside the
main method, we take advantage of the option to load the entire configuration of the application via the
from method and expand this with a test configuration using
with. In this test configuration (see Listing 6), we can now register Testcontainers as beans and use the
@ServiceConnection annotation to ensure that these are used as the connection.
The application can now be launched locally by starting the TestApplication class in our IDE or by running the new Maven goal
spring-boot:test-run or the Gradle task
bootTestRun. If the defaults of a
@ServiceConnection are insufficient for our needs or if we have to configure additional properties, it is also possible – similar to the tests – to use the
DynamicPropertyRegistry within the bean registration of a test configuration (see Listing 7).
If we use the
spring-boot-devtools during development, we see that a new container is also started after reloading the application following a change. This may be desirable but also means that all previously created data will be lost after every reload. If we wish to avoid this, we can extend the bean registration of the Testcontainer with the annotation
@RestartScope (see Listing 8).
Alternatively, we could use the (still experimental) feature for reusable containers. However, because this deviates from the procedure documented in Spring Boot and is still experimental, I would advise using
Local development with Docker Compose
In my most recent projects, it was typical to use Docker Compose to start the external services required for development. A compose.yml file existed for this purpose (see Listing 9), and
docker compose up had to be run before starting the application. After the application had been stopped,
docker compose down could be used to ensure that the external services were also stopped.
Precisely this workflow is now directly supported in Spring Boot 3.1. For this, we must add the new
spring-boot-docker-compose as a dependency, as shown in Listing 10. If we now start our application, we can see in the log (see Listing 11) that our compose.yml was detected and the
postgres service defined there was started.
By default, however,
stop is used for stopping rather than
down. The container is then retained even after we stop the application. If we want to change this, we can do so via the configuration value
spring.docker.compose.stop.command. A similar situation applies to the location and name of the Docker Compose file. These can be changed via
In addition to starting and stopping the services defined within Docker Compose, Service Connections are also automatically generated here, as with the Testcontainer support. To identify which type of Service Connection is provided by a service, the name of the container image is analyzed. If this doesn’t work because a custom image is being used, there is a way to specify this yourself, as seen in Listing 12. Listing 12 also shows how to define a service that is started and stopped simultaneously with the application but for which no Service Connection should be created.
To identify when a service has been started successfully, the defined
healthcheck from the Docker Compose file is used. If nothing is defined here, Spring Boot Docker Compose waits until the defined port is reachable via TCP. This can also be switched off, and the default timeouts can be changed as well.