Java Microservice Development: Preparing for Spring Boot 3.X

Introduction

For a long time, the Java microservice SDK only supported Spring Boot 2.X. We are pleased to announce that starting with version 1020.150.1, the microservice SDK now uses Spring Boot 3. In this article, I will describe how you can prepare your existing Java microservice for Spring Boot 3.X.

The effort required to upgrade to a new version depends on the libraries and technologies used in your project. If you explicitly use any of the Spring updated libraries, you will probably need to adjust it after switching to new microservices SDK. However, in most cases, only minor changes to your code will be necessary. In the first section Basic Changes, I will list the basic steps you must follow to configure your project to the new microservice SDK version. In the Troubleshooting section, I will list some common issues you might encounter after the basic changes. I will try to use all the known compile error and exception messages so that you can search in this document more easily.

Before we start let me give some more details about the new versions.

Versions and change strategy

:warning: Important
Tenants configured to use the OAI-secure authentication method will only function correctly with the latest maintenance release of the Cumulocity component on the server side. It needs to be at least 1020.651.0 for public environments, 1018.540.258 for the y2024 release. There is no active check in the SDK to ensure this prerequisite is met. Please check your tenant if you use OAI-secure: Basic settings - Cumulocity documentation

The new microservices SDK has updated the versions of various third-party dependencies such as Spring Boot (to 3.3), Spring Security (6.3), Spring core (6.1)

Depending on your microservice and Spring library usage you might want to perform the update gradually to limit the required code changes at a single step.

To support this process, the following microservices SDK versions are available:

If you do not want to update your microservice gradually, you can also directly use microservices SDK version 1020.155.0 using Spring Boot 3.3 without performing the intermediate steps.

Irrespective of the way you perform the update - directly or gradually - you can consult the Spring Boot Migration guide and the respective release notes listed below:

Basic Changes

Java 17

Spring Boot 3.X will require Java 17. So please configure your runtime to use Java 17 and your compiler level to the correct version. This also counts to your CI/CD pipelines.

Change the Java version in your pom.xml to Java 17:

<properties>
    <java.version>17</java.version>
    <maven.compiler.source>${java.version}</maven.compiler.source>
    <maven.compiler.target>${java.version}</maven.compiler.target>
    ...
</properties>

More recent Java versions are supported as well.

Change your dependencies

Upgrade your microservice SDK version, if you followed the documentation you have to update the property c8y.version. For the build plugin you need also to setup the spring boot version int the property spring-boot-dependencies.version.

<properties>
    <java.version>17</java.version>
    <maven.compiler.source>${java.version}</maven.compiler.source>
    <maven.compiler.target>${java.version}</maven.compiler.target>
    <c8y.version>1020.155.0</c8y.version>
    <spring-boot-dependencies.version>3.3.5</spring-boot-dependencies.version>
    ...
</properties>

Documentation Microservice Dependencies

Compile and Build

Refresh (clean) your project in your IDE or using Maven:

mvn clean install

The compiler will now tell you what issues you have to resolve. If your project is compiling without errors you are already finished :partying_face:. You should also check that your CI/CD pipeline is working!

However, it will most likely that you will have some compile errors! Let us search in the next section how to fix them. If you identify additional issues, please also comment under the article!

Troubleshooting

Check for deprecated classes and methods

You should check for deprecated classes and methods, and substitute those in your code. Please consult the Spring Boot deprecated list.

Spring 3.0.x Deprecated-List
Spring 3.1.x Deprecated-List
Spring 3.2.x Deprecated-List

Example:

org.springframework.scheduling.support.CronSequenceGenerator //has been deprecated
org.springframework.scheduling.support.CronExpression //use instead

//If you have the following code snippet
import org.springframework.scheduling.support.CronSequenceGenerator;
import java.util.Date;
sequenceGenerator = new CronSequenceGenerator(stringBuilder.toString());
IDate periodStartDate = sequenceGenerator.next(new Date());

//adjust it to 
import org.springframework.scheduling.support.CronExpression;
import java.time.Instant;
cronExpression = CronExpression.parse(stringBuilder.toString());
Instant periodStartDate = cronExpression.next(Instant.now());

Check for version updates of third-party dependencies

If you have third-party dependencies, they might have become incompatible with the new versions used in microservices SDK 1020.155.0. In this case, you need to update your third-party dependencies to comply with the newer Spring version.

Spring Boot 3.0 uses Jakarta EE 10. Use jakarta instead of javax

Jakarta EE now uses jakarta packages instead of javax. You might need to update the respective dependencies and import statements in your project.

Example:

import javax.validation.*; //removed
import jakarta.validation.*; //use instead

If you get following runtime exception (java.lang.IllegalStateException: No target Validator set) you must be aware that you need an implementation of the Jakarta Bean Validation API (formerly known as Java Bean Validation or JSR 380).

Caused by: java.lang.IllegalStateException: No target Validator set
	at org.springframework.util.Assert.state(Assert.java:76)
	at org.springframework.validation.beanvalidation.SpringValidatorAdapter.forExecutables(SpringValidatorAdapter.java:403)
	at org.springframework.validation.beanvalidation.MethodValidationAdapter.invokeValidatorForArguments(MethodValidationAdapter.java:257)
	at org.springframework.validation.beanvalidation.MethodValidationAdapter.validateArguments(MethodValidationAdapter.java:240)
	at org.springframework.web.method.annotation.HandlerMethodValidator.validateArguments(HandlerMethodValidator.java:115)

The spring-boot-starter-validation dependency provides the necessary libraries and configuration to enable validation in a Spring Boot application. You need to add following dependency to your pom file.

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-validation</artifactId>
</dependency>

For more information see: Java Bean Validation

Spring Security 6

If your microservice defines security configuration, it is recommended that you consult the Spring Security 6 migration guide, which contains a lot of examples regarding the needed adjustments. Still, among the most notable changes are:

Annotation EnableGlobalMethodSecurity is deprecated

The annotation @EnableGlobalMethodSecurity is now deprecated in the used Spring Security versions. Therefore, if you are using it, you should migrate to @EnableMethodSecurity.

New default values come along this change as follows:

boolean prePostEnabled() default true;
boolean jsr250Enabled() default false;
boolean proxyTargetClass() default false;

It is worth saying that pre-post annotations need to be disabled explicitly if required.

antMatchers() is deprecated

Securing requests using antMatchers() is deprecated in Spring Security 6, and has been removed from the API. Instead, requests can be secured by using the overloaded method requestMatchers().

If you are defining a securityFilterChain and use antMatchers(), you will have to substitute it.

Example:

Old Version:

@EnableWebSecurity
@Configuration
public class AppSpecificSecurityConfig {

	@Bean
	public SecurityFilterChain apiFilterChanin(HttpSecurity http) throws Exception {
		http.antMatcher("/v3/api-docs");
		return http.build();
	}
}

New Version:

@EnableWebSecurity
@Configuration
public class AppSpecificSecurityConfig {

	@Bean
	public SecurityFilterChain apiFilterChanin(HttpSecurity http) throws Exception {
		http.authorizeHttpRequests(authorizeRequests ->
			authorizeRequests
				.requestMatchers("/v3/api-docs").permitAll() // Exclude this path from security
				.anyRequest().authenticated()
		)
		.formLogin(withDefaults());

		return http.build();
	}
}

Removal of custom SecurityExpressionRoot from the SDK in version 1020.140.0

see for more details here

WebSecurityConfigurerAdapter is deprecated

If you have a security configuration class which extends WebSecurityConfigurerAdapter , your configuration class should not extend it anymore, and should not override the configure(HttpSecurity http) method. Instead, you can register a SecurityFilterChain bean to cover the needed functionality. If your configuration class overrides configure(WebSecurity web), you can register a WebSecurityCustomizer bean instead.

More information on the deprecation can be found on the provider’s website.

Handling of HTTP status codes

The handling of HTTP status codes has changed in Spring Boot 3.3.3. Previously, these were a fixed set of predefined values represented as enums in HttpStatus. Some of these enums are now deprecated.

import org.springframework.http.HttpStatus;

protected boolean hasError(HttpStatus statusCode) {

protected boolean hasError(HttpStatusCode statusCode) {

The HttpStatusCode interface has 8 instance methods that check response categories:

  • is1xxInformational()
  • is2xxSuccessful()
  • is3xxRedirection()
  • is4xxClientError()
  • is5xxServerError()
  • isError()
  • isSameCodeAs(HttpStatusCode other)
  • value()

The signatures of functions such as getStatusCode() and put have also changed, returning HttpStatusCode instead of HttpStatus.

Example:

import org.springframework.http.HttpStatusCode;
HttpStatusCode statusCode = response.getStatusCode();
public <T> HttpStatus put(..) {
public <T> HttpStatusCode put(..) {

Annotation type LocalServerPort

org.springframework.boot.web.server.LocalServerPort
has been deprecated since Spring Boot 2.7.0 and finally removed in 3.0.0 in favour of:
org.springframework.boot.test.web.server.LocalServerPort

Because of this change, you need to substitute the import statement, as follows:

import org.springframework.boot.web.server.LocalServerPort; //remove
import org.springframework.boot.test.web.server.LocalServerPort; //use instead

More information can be found on the provider’s website

URL matching changes in Spring MVC and WebFlux

The default of the trailing slash matching configuration option has changed to false as of Spring Framework 6.0. If you have a controller, as the one shown below, “GET /data/” will not match anymore:

  @GetMapping("/data")
    String data() {
        return "Hello from /data";
    }

This change might require special treatment of the trailing slash in your code.

Upgrade RestTemplate to HttpClient 5

As of Spring Framework 6.0, support for Apache HttpClient 4 with RestTemplate has been removed and replaced by org.apache.httpcomponents.client5:httpclient5. If you are using org.springframework.web.client.RestTemplate, you have to consider this change, and use httpclient5 instead.

You can consult the website of the provider regarding the changes introduced to httpclient5.

Compiling with the -parameters option

As of Spring Boot 3.2 the used Spring Framework version does not infer parameter names by parsing bytecode. This can lead to issues with dependency injection or property binding. To avoid such issues, compile with the -parameters option.

For instance, if you are using maven-compiler-plugin, specify <parameters>true</parameters> , as in the example below.

       <artifactId>maven-compiler-plugin</artifactId>
          <version>__X_</version>
          <configuration>
            <source>${java.version}</source>
            <target>${java.version}</target>
            <parameters>true</parameters>
          </configuration>
4 Likes