SoatDev IT Consulting
SoatDev IT Consulting
  • About us
  • Expertise
  • Services
  • How it works
  • Contact Us
  • News
  • June 6, 2023
  • Rss Fetcher

A few actionable steps to prepare for your Spring 3 migration

Photo by Egor on Unsplash

Prepare for Spring 3 migration

As the last spring-boot version 2.7.x is close to ending support(Nov 18, 2023), I started migrating my project to Spring Boot 3 and wrote down the most important issues I encountered during the migration.

  • Java 17
  • Jakarta EE
  • Kafka
  • OpenAPI
  • Security

There are already many migration guides, but I think it can start from these two documents:

  • Preparing for Spring Boot 3.0
  • Spring Boot 3.0 Migration Guide

After completing the Spring 3 migration, eventually, my project dependencies will look like this:

+----------------------+---------+
| Lib | Version |
+----------------------+---------+
| spring-boot | 3.0.6 |
| spring-core | 6.0.8 |
| spring-security-core | 6.0.3 |
| spring-webmvc | 6.0.8 |
| httpclient5 | 5.1.4 |
| spring-kafka | 3.0.6 |
| spring-kafka-test | 3.0.6 |
| kafka-clients | 3.3.2 |
+----------------------+---------+

Java 17

The minimum Java version required by Spring 3 is Java 17

Instant precision

Java 11 - Instant: 2022-12-12T18:04:27.267229Z
Java 17 - Instant: 2022-12-12T18:04:27.267229114Z

On certain platforms, Instant will get nine decimal digits. However, Postgres, MySQL and Kafka still store six decimal digits. Hence in some unit tests, the original timestamp (nine decimal digits) is not equal to the transformed one (six decimal digits).

Solution

Truncate the original timestamp into microseconds with the following command:

Instant.now().truncatedTo(ChronoUnit.MICROS)

Refer to the following references:

  • Changes in Instant.now() between Java 11 and Java 17 in AWS Ubuntu standard 6.0
  • Java 15 nanoseconds precision causing issues in the Linux environment

Jakarta EE

As javax namespace is removed from Java 17, Jakarta EE 9 is the minimum requirement in the Spring 3 release notes.

I would advise you to replace all javax to jakarta in this article, e.g.,

javax.persistence.EmbeddedId
javax.persistence.Entity
javax.persistence.Lob
javax.persistence.Table

to

jakarta.persistence.EmbeddedId
jakarta.persistence.Entity
jakarta.persistence.Lob
jakarta.persistence.Table

There should be a jakarta package instead of a javax package in the dependencies. Here’s what that looks like:

jakarta.persistence-api
jakarta.validation-api
jakarta.annotation-api
jakarta.servlet-api

Apache HttpClient in RestTemplate

Support for Apache HttpClient has been removed in Spring Framework 6.0 and immediately replaced by org.apache.httpcomponents.client5:httpclient5 (Note: this dependency has a different groupId). If you are noticing issues with HTTP client behavior, that RestTemplate is falling back to the JDK client.

org.apache.httpcomponents:httpclient can be brought transitively by other dependencies, so your application might rely on this dependency without declaring it.

See the Spring-Boot-3.0-Migration-Guide.

Here is my customised resttemplate example:

fun RestTemplateBuilder.withCustConnectionPool(maxConnTotal:Int): RestTemplateBuilder  {
val connectionManager = PoolingHttpClientConnectionManagerBuilder.create()
.setMaxConnTotal(maxConnTotal)
.build()
val client = HttoClientBuilder.create().apply {
// customized
}.setConnectionManager(connectionManager).build()

val rf: Supplier<ClientHttpRequestFactory> = Supplier { HttpComponentsClientHttpRequestFactory(client) }
return this.requestFactory(rf)
}

ConstructingBinding annotations

The ConfigurationProperties class annotation @ConfigurationProperties is no longer required by default to mark constructions with @ConstructorBinding, and you should remove it from the configuration class unless the configuration class has multiple constructors to configure the property binding explicitly.

It is mentioned in another Spring 3 migration guide.

OpenAPI generator

We often use OpenAPI generator to create the contracts. To generate code and provide dependencies for use with Spring Boot 3.x and use jakarta instead of javax in imports:

"useSpringBoot3" to "true",
"useJakartaEe" to "true"

Refer to the following reference guide:

Documentation for the spring Generator | OpenAPI Generator

The relevant dependencies are:

implementation("org.yaml:snakeyaml:2.0")
implementation("io.swagger.parser.v3.swagger-parser:2.1.15")
implementation("org.openapitools:openapi-generator-gradle-pluin-api:6.5.0")

Security

WebSecurityConfigurerAdapter is deprecated in Spring 3.

This can be change from:

@Configuration
open class SecurityConfiguration: WebSecurityConfigurerAdapter() {

@Override
override fun configure(val http: HttpSecurity) {
http {
authorizeHttpRequests {
authorize(anyRequest, authenticated)
}

httpBasic {}
}
}
}

to

@Configuration
open class SecurityConfiguration {
@Bean
fun filterChain(http: HttpSecurity): SecurityFilterChain {
http {
authorizeHttpRequests {
authorize(anyRequest, authenticated)
}
httpBasic {}
}
return http.build()
}
}

and In-Memory Authentication can be changed from

@Configuration
open class SecurityConfiguration: WebSecurityConfigurerAdapter() {
override fun configure(auth: AuthenticationManagerBuilder auth) {
auth.inMemoryAuthentication()
.withUser("username")
.password("{noop}pass")
.authorities(listOf())
}
}

to

@Configuration
open class SecurityConfiguration {
@Bean
fun userDetailService():InmemoryUserDetailsManager {
val user: UserDetails = User.withDefaultPasswordEncoder()
.username("username")
.password("pass")
.authorities(listOf())
.build()
return InMemoryUserDetailsManager(user)
}
}

Note: You can’t use {noop} to indicate the encryptor

Refer to these guides for more information:

Configuration Migrations

and

Spring Security without the WebSecurityConfigurerAdapter

Spring-Kafka

Transaction-Id

The transactional.id property of each producer is transactionIdPrefix + n, where n starts with 0 and is incremented for each new producer. In previous versions of Spring for Apache Kafka, the transactional.id was generated differently for transactions started by a listener container with a record-based listener to support fencing zombies.

This is not necessary anymore, with EOSMode.V2 being the only option starting with 3.0. For applications running with multiple instances, the transactionIdPrefix must be unique per instance.

The transaction-id-prefix design has been changed since 3.0. Refer to this commit:

Remove outdated information for transactional.id (#2524) · spring-projects/spring-kafka@7e336d1

In spring-kafka 3, we need to assign a unique Transaction-Id-Prefix for each application instance. The usual approach is to use a random string/hostname, as shown below:

fun uniqueTransactionIdPrefix(producerOnly: String = "TX-") {
return InetAddress.getLocalHost().getHostName() + this.transactionIdPrefix + producerOnly
}

Note: the producerOnly is used to differentiate a consumer-initiated producer or a producer-only.

It is not an ideal solution. As in the Kubernetes environment, the hostname could change after crash/restart. However, the restart might take longer than the transaction.timeout.ms, so it should be fine.

Refer to the following Stack Overflow question for more information:

transaction-id-prefix need to be identical for same application instance in spring kafka 3?

More about Kafka transaction

This article describes the Kafka transaction and idempotency:

Transactions in Apache Kafka | Confluent

and Spring’s official docs is another treasure:

Spring for Apache Kafka

This link is about how to configure transaction-id-prefix in spring-kafka 2.x. It is still helpful in spring 3 to understand the design of kafka-producer.

Basically, the Kafka producer has a unique ID, such as transaction-id-prefix-atomic_inc_id When it wants to use kafkatemplate to send data, it will take an idle producer from the cache, and return/close it after committing a transaction. The producer’s epoch will also be bumping after the transaction is committed.

An issue we often observe is ProducerFencedException, which means a producer has a higher epoch, but the other one (or zombie) with the same producerId still tries to commit it with a lower epoch; hence it gets fenced. It is caused by a client rebalance or a programming bug.

Refer to the following Stack Overflow question for more information:

Why my Spring Kafka unit test almost ran into ProducerFencedException every time

KafkaTemplate changes

In version 3.0, the methods that previously returned ListenableFuture have been changed to return CompletableFuture. To facilitate the migration, the 2.9 version added a method usingCompletableFuture() that provided the same methods with CompletableFuture return types. This method is no longer available.

It changes from this:

kafkaTemplate.send(producerRecord).addCallback(
{
result: SendResult<Any?, Any?>? ->
log.info("xxxx")
},
{
ex: Throwable? ->
log.error("xxx")
}
)

to

kafkaTemplate.send(producerRecord).whenComplete { result, ex ->
if (ex!=null) {
// exception
} else {
// successfully sent
}
}

JacksonObjectMapper

Problem

No Serializer found for class org.springframework.http.HttpMethod and no properties discovered to create BeanSerializer

The HttpMethod member variable name is changed to private in Spring 3, hence ObjectMapper is unable to serialize the private variable

Solution

Customize the serializer or allow jacksonObjectMapper to serialize private properties.

jacksonObjectMapper().setVisibility(PropertyAccessor.FIELD, JsonAutoDetect.Visibility.ANY)

Happy coding!


Spring Boot 3 Migration was originally published in Better Programming on Medium, where people are continuing the conversation by highlighting and responding to this story.

Previous Post
Next Post

Recent Posts

  • Valla raises $2.7M to make legal recourse more accessible to employees
  • Console raises $6.2M from Thrive to free IT teams from mundane tasks with AI
  • Former DreamWorks CEO Jeffrey Katzenberg co-leads $15.5M Series A for AI video ad platform
  • Microsoft Bing gets a free Sora-powered AI video generator
  • Snowflake to acquire database startup Crunchy Data

Categories

  • Industry News
  • Programming
  • RSS Fetched Articles
  • Uncategorized

Archives

  • June 2025
  • May 2025
  • April 2025
  • February 2025
  • January 2025
  • December 2024
  • November 2024
  • October 2024
  • September 2024
  • August 2024
  • July 2024
  • June 2024
  • May 2024
  • April 2024
  • March 2024
  • February 2024
  • January 2024
  • December 2023
  • November 2023
  • October 2023
  • September 2023
  • August 2023
  • July 2023
  • June 2023
  • May 2023
  • April 2023

Tap into the power of Microservices, MVC Architecture, Cloud, Containers, UML, and Scrum methodologies to bolster your project planning, execution, and application development processes.

Solutions

  • IT Consultation
  • Agile Transformation
  • Software Development
  • DevOps & CI/CD

Regions Covered

  • Montreal
  • New York
  • Paris
  • Mauritius
  • Abidjan
  • Dakar

Subscribe to Newsletter

Join our monthly newsletter subscribers to get the latest news and insights.

© Copyright 2023. All Rights Reserved by Soatdev IT Consulting Inc.