
Book's Upgrade: Migrating from Spring Boot 2.4 to 2.5
This chapter describes how to upgrade the book’s source code to Spring Boot 2.5.5 and Java 17. No major changes but some parts of the code break, so we need to make some little adjustments here and there. We’ll also learn how to use the new RandomGenerator interface in tests.
- No major complications, but the update breaks the code
- Migrate to Spring Boot 2.5.5 and Java 17
- No more spying on Randoms
- Removing application-default.properties
- Summary
No major complications, but the update breaks the code
The last upgrade of the book repositories to Spring Boot 2.4 was quite extensive mainly because of the upgrade to a new major version of Spring Cloud (2020.0.0). This time, the impact on the book’s code is much smaller.
We’ll take as a baseline the last changes to upgrade the projects to Spring Boot 2.4. The code is available on GitHub (while you’re there, star it!).
Migrate to Spring Boot 2.5.5 and Java 17
First, we need to download and install the JDK version 17 in case we haven’t done that already.
Then, we update the pom.xml
files in all the Spring Boot projects. See Listing 1 as an example (multiplication service).
- The version of the
spring-boot-starter-parent
dependency has been set to 2.5.5. All the library dependencies will be also upgraded following this parent pom. - The
java.version
property is now17
, which means we want to build our project using the JDK 17. Make sure you set yourJAVA_HOME
environment variable accordingly. - The project version has changed to
2.5.5-SNAPSHOT
to make it consistent with Spring Boot versioning. We could use any other versioning approach but this is convenient for the book upgrades. - We upgraded Spring Cloud to the latest version,
2020.0.4
.
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.5.5</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>microservices.book</groupId>
<artifactId>multiplication</artifactId>
<version>2.5.5-SNAPSHOT</version>
<name>multiplication</name>
<description>Multiplication Microservice - Learn Microservices with Spring Boot - Upgrade 2.5.5</description>
<properties>
<java.version>17</java.version>
<spring-cloud.version>2020.0.4</spring-cloud.version>
</properties>
<dependencies>
<!-- ... they remain the same, versions are updated because of the parent -->
</dependencies>
</project>
Listing 1. Upgrading Spring Boot and Java
No more spying on Randoms
The JDK 17 introduces some changes around the Random
class that breaks one of the tests in the Multiplication repository. See Listing 2 for the offending test.
@ExtendWith(MockitoExtension.class)
public class ChallengeGeneratorServiceTest {
private ChallengeGeneratorService challengeGeneratorService;
@Spy
private Random random;
@BeforeEach
public void setUp() {
challengeGeneratorService = new ChallengeGeneratorServiceImpl(random);
}
@Test
public void generateRandomFactorIsBetweenExpectedLimits() {
// 89 is max - min range
given(random.nextInt(89)).willReturn(20, 30);
// when we generate a challenge
Challenge challenge = challengeGeneratorService.randomChallenge();
// then the challenge contains factors as expected
then(challenge).isEqualTo(new Challenge(31, 41));
}
}
Listing 2. The ChallengeGeneratorServiceTest class
When we run this test with Java 17, we get this error:
Mockito cannot mock this class: class java.util.Random.
Followed by the root cause, a few lines below:
Underlying exception : java.lang.IllegalStateException: Cannot access annotation property public abstract boolean jdk.internal.util.random.RandomSupport$RandomGeneratorProperties.isStochastic()
Instead of trying to figure out why this doesn’t work, we can use this opportunity to improve our code and remove the existing trick. We were using Mockito.spy()
to change the behavior of an existing Random
object and make it return the numbers we want instead of random numbers. We did this because there was no better way to mock a plain-java Random generator before JDK 17.
The good news is that now there is a better way. Random
implements the new interface RandomGenerator
(see Javadoc). Mocking interfaces with Mockito is simpler than Spying objects, and it’s a better practice.
Therefore, we can change our implementation of ChallengeGeneratorServiceImpl
to accept the interface instead of the specific implementation. See Listing 3. Now, the constructor we created for tests accepts and assigns a RandomGenerator
object. The default constructor assigns a Random
object to it, which works because this class implements this interface from JDK 17 onwards.
package microservices.book.multiplication.challenge;
import org.springframework.stereotype.Service;
import java.util.Random;
import java.util.random.RandomGenerator;
@Service
public class ChallengeGeneratorServiceImpl implements ChallengeGeneratorService {
private final static int MINIMUM_FACTOR = 11;
private final static int MAXIMUM_FACTOR = 100;
private final RandomGenerator randomGenerator;
ChallengeGeneratorServiceImpl() {
this.randomGenerator = new Random();
}
protected ChallengeGeneratorServiceImpl(final RandomGenerator randomGenerator) {
this.randomGenerator = randomGenerator;
}
private int next() {
return randomGenerator.nextInt(MAXIMUM_FACTOR - MINIMUM_FACTOR) + MINIMUM_FACTOR;
}
@Override
public Challenge randomChallenge() {
return new Challenge(next(), next());
}
}
Listing 3. The ChallengeServiceGeneratorServiceTest class
With this new code in place, we can alter the test to look much more straightforward. We simply need to use a @Mock
annotation over the interface, and instruct Mockito to return the predefined values as we did before. See Listing 4.
@ExtendWith(MockitoExtension.class)
public class ChallengeGeneratorServiceTest {
private ChallengeGeneratorService challengeGeneratorService;
@Mock
private RandomGenerator randomGenerator;
@BeforeEach
public void setUp() {
challengeGeneratorService = new ChallengeGeneratorServiceImpl(randomGenerator);
}
@Test
public void generateRandomFactorIsBetweenExpectedLimits() {
// 89 is max - min range
given(randomGenerator.nextInt(89)).willReturn(20, 30);
// when we generate a challenge
Challenge challenge = challengeGeneratorService.randomChallenge();
// then the challenge contains factors as expected
then(challenge).isEqualTo(new Challenge(31, 41));
}
}
Listing 4. The new ChallengeServiceGeneratorServiceTest class
Removing application-default.properties
Prior to this last update of Spring Boot, I created a application-default.properties
file under the test
resource folder in the Gamification service. The only setting configured there was the active profile. See Listing 5.
spring.profiles.active=test
Listing 5. The application-default.properties
file
Since the default profile in Spring Boot is named default
, this property would be loaded and therefore the other file application-test.properties
would be also processed in search of additional properties. The contents of that file are shown in Listing 6, and the goal is to disable Spring Cloud Consul for tests.
spring.cloud.consul.enabled=false
Listing 6. The application-test.properties
file
That strange setup was necessary to make all the Spring Boot tests pick up that configuration without needing to add an annotation to all of them (see this SO thread). However, when I upgraded to the latest Spring Boot version and ran the tests for the Gamification service I got an error. See Listing 7.
java.lang.IllegalStateException: Failed to load ApplicationContext
Caused by: org.springframework.boot.context.config.InvalidConfigDataPropertyException: Property 'spring.profiles.active' imported from location 'class path resource [application-default.properties]' is invalid in a profile specific resource [origin: class path resource [application-default.properties] - 1:24]
Listing 7. Can’t use a default profile to point to an active profile
That solution doesn’t work anymore. Accidentally, I introduced the annotations in the Gamification service tests that point to a new test.properties
file which contains the property to disable Consul. That means both the application-test.properties
and application-default.properties
are unnecessary, and we can safely remove them. See Listing 8 for an example of an annotation that points to the test.properties
file.
@SpringBootTest
@TestPropertySource("classpath:/test.properties")
class GamificationApplicationTests {
@Test
void contextLoads() {
}
}
Listing 7. Can’t use a default profile to point to an active profile
Summary
That’s all, both the Gateway and the Logs service don’t require changes apart from the pom.xml
updates. You can rebuild the Docker images and give a try to the Docker configuration. I also changed the docker-compose.yml
file in case you want to see the result.
If you still don’t have the book, I strongly recommend you get your copy now. I keep updating the book’s source code and will be sharing more content around the same use case.

Comments