KINTO Tech Blog
Development

Spring Boot 2 to 3 Upgrade: Procedure, Challenges, and Solutions

Cover Image for Spring Boot 2 to 3 Upgrade: Procedure, Challenges, and Solutions

Spring Boot 2 to 3 Upgrade: Procedure, Challenges, and Solutions

Introduction

Hello. I am Takehana from the Payment Platform Team / Common Service Development Group [1][2][3][4] / Platform Development Division.
This article covers the latest Spring Boot update which we use for payment platform APIs and batches.

Challenges to Solve and Goals I Wanted to Achieve

  • I am using Spring Boot 2, and I want to upgrade to 3 in consideration of the support period and other factors.
  • The version of the library used was also upgraded
Library Before migration (2) After migration (3)
Java 17 No changes
MySQL 5.7 8.0
Spring Boot 2.5.12 3.1.0
Spring Boot Security 2.5.12 3.1.0
Spring Boot Data JPA 2.5.12 3.1.0
hibernate Types 2.21.1 3.5.0
MyBatis Spring Boot 2.2.0 3.0.2
Spring Batch 4.3 5.0
Spring Boot Batch 2.5.2 3.0.11
Spring Boot Cloud AWS 2.4.4 3.0.1

Trial and Error and Measures Taken

Method of Application

We first updated and deprecated libraries that have little impact with existing code while referring to the official migration guide.
After that, we updated to 3.1.0 and continued fixing, building, testing, and adjusting.

Spring Boot 3.1 Release Notes
Spring Boot 3.0 Migration Guide
Spring Batch 5.0 Migration Guide

javaxJakarta

We changed packages from javax, which affected many files, to Jakarta.
The name after the package root did not change, so we replaced it mechanically.

Around DB access

MySQL-Connector-Java

I changed to mysql-connector-j because it was migrated.
Maven Repository

MySQLDialect

Using org.hibernate.dialect.MySQLDialect allows it to absorb MySQL version differences.
image1.png

Hibernate-Types

The method of setting the Json type used with JPA Entity has changed with the upgrade.
image2.png

Change ID generate to IDENTITY

The automatic numbering method has changed in Spring DATA JPA, and it now requires the table XXX_seq when using AUTO.
Since our system used MySQL's Auto Increment, we decided not to use the numbering feature with JPA.
image3.png

Spring Batch

Modifying the Meta Table

The structure of the Spring Batch management table changed.
The existing table was changed using the migration guide as a reference and using the following.
/org/springframework/batch/core/migration/5.0/migration-mysql.sql

However, just executing the ALTER TABLE caused an error during operation due to existing data, so we decided to restore the data to its initial state after confirming that it would not affect future operation.

Caused by: java.lang.RuntimeException: java.lang.ClassNotFoundException: LONG
	at org.springframework.batch.core.repository.dao.JdbcJobExecutionDao$2.processRow(JdbcJobExecutionDao.java:468)
...

(The PARAMETER_TYPE of BATCH_JOB_EXECUTION_PARAMS contained a LONG value)

The data was restored to its initial state with the following SQL.

TRUNCATE TABLE BATCH_STEP_EXECUTION_CONTEXT;
TRUNCATE TABLE BATCH_STEP_EXECUTION_SEQ;
TRUNCATE TABLE BATCH_JOB_SEQ;
TRUNCATE TABLE BATCH_JOB_EXECUTION_SEQ;
TRUNCATE TABLE BATCH_JOB_EXECUTION_PARAMS;
TRUNCATE TABLE BATCH_JOB_EXECUTION_CONTEXT;

SET foreign_key_checks = 0;
TRUNCATE TABLE BATCH_JOB_EXECUTION;
TRUNCATE TABLE BATCH_JOB_INSTANCE;
TRUNCATE TABLE BATCH_STEP_EXECUTION;
SET foreign_key_checks = 1;

INSERT INTO BATCH_STEP_EXECUTION_SEQ VALUES(0, '0');
INSERT INTO BATCH_JOB_EXECUTION_SEQ VALUES(0, '0');
INSERT INTO BATCH_JOB_SEQ values(0, '0');

BasicBatchConfigurer can no longer be used

The method of use was changed to DefaultBatchConfiguration.

StepBuilderFactory and JobBuilderFactory were deprecated

JobRepository and TransactionManager are now passed with new StepBuilder().
image4.png

The argument type of ItemWriter changed

The write process was fixed after List changed to org.springframework.batch.item.Chunk.

Before correction After correction
<pre>ItemWriter<Dto> write() {
return items -> {
...
items.stream()
.flatMap(dto -> dto.getDatas().stream())
.forEach(repository::update);
...</pre>
<pre>ItemWriter<Dto> write() {
return items -> {
...
items.getItems().stream()
.flatMap(dto -> dto.getDatas().stream())
.forEach(repository::update);
...</pre>

The behavior of @EnableBatchProcessing changed

When checking operations, the process was skipped with chunk model batches.
The behavior of @EnableBatchProcessing changed.

Spring Cloud AWS

Library changes

This system uses many AWS services, and Spring Cloud AWS was used to link them.
With the update, io.awspring.cloud:spring-cloud-starter-aws was changed to io.awspring.cloud:spring-cloud-aws-starter (confusing), and com.amazonaws:aws-java-sdk was replaced with software.amazon.awssdk and fixed so it would operate.
Spring Cloud AWS

SES

Regarding SES, because AmazonSimpleEmailService is no longer usable, we switched to JavaMailSender for the implementation.
The JavaMailSender used is built with SES Auto Configuration and used with DI.

SQS

Objects for requests such as sending to SQS are made with the Builder pattern, so we fixed them accordingly.
In addition, @NotificationMessage used in SQSListener is gone, so we created SqsListenerConfigurer and prepared MessageConverter.
image5.png

@Bean
public SqsListenerConfigurer configurer(ObjectMapper objectMapper) {
  return registrar ->
      registrar.manageMessageConverters(
          list ->
              list.addAll(
                  0,
                  List.of(
                      new SQSEventModelMessageConverter(
                          objectMapper, ReciveEventModel.class),
...
}

@RequiredArgsConstructor
private static class SQSEventModelMessageConverter implements MessageConverter {

  private static final String SQS_EVENT_FILED_MESSAGE = "Message";

  private final ObjectMapper objectMapper;

  private final Class<?> modelClass;

  @Override
  public Object fromMessage(Message<?> message, Class<?> targetClass) {
    if (modelClass != targetClass) {
      return null;
    }

    try {
      val payload =
          objectMapper
              .readTree(message.getPayload().toString())
              .get(SQS_EVENT_FILED_MESSAGE)
              .asText();
      return objectMapper.readValue(payload, targetClass);
    } catch (IOException ex) {
      throw new MessageConversionException(
          message, " Could not read JSON: " + ex.getMessage(), ex);
    }
...
}

S3

For uploads to S3, TransferManager was changed to S3TransferManager, and the implementation of issuing signed URLs needed to be fixed.
image6.png

SNS

The sns:CreateTopic privilege was required with DefaultTopicArnResolver for sending SNS.
We made it so TopicsListingTopicArnResolver can be used now, and CreateTopic permission is no longer needed.

@ConditionalOnProperty("spring.cloud.aws.sns.enabled")
@Configuration
public class SNSConfig {
  @Bean
  public TopicArnResolver topicArnResolver(SnsClient snsClient) {
    return new TopicsListingTopicArnResolver(snsClient);
  }
}

Around the API

WebSecurityConfigurerAdapter cannot be referenced

We switched to a method that uses SecurityFilterChain referring to links.
spring-security

Making stricter URL paths

Trailing Slash paths are now strictly differentiated.
Since this system was linked with another internal system, we added a path to @RequestMapping, adjusted it with the peer system, and removed the added path.

Before addition After addition
<pre>...
@RequestMapping(
method = RequestMethod.GET,
value = {"/api/payments/{id}"},
...
</pre>
<pre>...
@RequestMapping(
method = RequestMethod.GET,
value = {"/api/payments/{id}", "/api/payments/{id}/"},
...
</pre>

Renaming properties (application.yml)

Properties such as jpa, redis, and spring-cloud-AWS were renamed.
We adjusted them according to the official information.

Deployment

There is a 404 with ECS deployment

We reached the point where we could deploy to ECS and confirmed the launch in the application log, but when I accessed the API, there was a 404.
I checked and saw that the Health Check failed the ECS deployment.
With the help of our cloud platform engineers, we discovered that the AWS-opentelemetry-agent version used for telemetry data collection was outdated.
image7.png

By changing to a version of jar 1.23.0 or later, we can successfully deploy and check API communication.
OpenTelemetryJava provided by AWS

Results, additional knowledge and application proposals, next attempts, etc.

There are places that did not have the general implementation pattern of Spring Boot which meet various requirements.
The structure did not allow us to migrate easily with just migration guide sometimes.
We managed to release it after repeated trial and error.
I would like to thank the team for their continued work and reviews.
We will continue to address the following remaining issues while also taking advantage of the features that were improved in Spring Boot 3.

Swagger-UI

We put off upgrading this.
Since spring-fox is not yet compatible with Spring Boot 3, we are considering changing to springdoc-openapi.

Spring Batch + MySQL 8.0 + DbUnit

It results in an error if it meets some conditions.
It seems that it is related to Spring Batch transaction management (meta table operation), and we are looking into how to fix it.

Summary of the Lessons of This Article

  • We were able to upgrade Spring Boot by repeating the build and test while referring to the migration guide.
  • This update had a wide range of effects, but by preparing tests, we found out what we had to fix, and we were able to address them efficiently.
  • We also found problems by running operations such as trying to change @EnableBatchProcessing, so we also had to check by running operations.
  • Regarding JavaEE, with the change to Jakarta, we had to update the Spring Boot library and others.
  • Security is stronger (trailing slash rules are stricter, AuthFilter can be used with Ignore Path, etc.).
  • There were differences in the updates for each dependent library, and Spring Cloud AWS was especially different.
  • We may have needed to make less changes if we had upgraded libraries more frequently.

Thank you for reading This article.
I hope that it will be useful for those who are also considering upgrading.

脚注
  1. Posted by a member of the Common Services Development Group
    [Domain-Driven Design (DDD) is incorporated into a payment platform with a view to global expansion ] ↩︎

  2. Posted by a member of the Common Services Development Group
    [New Employees Develop a New System with Remote Mob Programming] ↩︎

  3. Posted by a member of the Common Services Development Group
    [Improving Deployment Traceability with JIRA and GitHub Actions] ↩︎

  4. Posted by a member of the Common Services Development Group
    [Building a Development Environment with VSCode Dev Container] ↩︎

Facebook

関連記事 | Related Posts

We are hiring!

【バックエンドエンジニア】my route開発G/東京

my route開発グループについてmy route開発グループは、my routeに関わる開発・運用に取り組んでいます。my routeの概要 my routeは、移動需要を創出するために「魅力ある地域情報の発信」、「最適な移動手段の提案」、「交通機関や施設利用のスムーズな予約・決済」をワンストップで提供する、スマートフォン向けマルチモーダルモビリティサービスです。

【プロダクト開発バックエンドエンジニア(リーダークラス)】共通サービス開発G/東京・大阪

共通サービス開発グループについてWebサービスやモバイルアプリの開発において、必要となる共通機能=会員プラットフォームや決済プラットフォームなどの企画・開発を手がけるグループです。KINTOの名前が付くサービスやKINTOに関わりのあるサービスを同一のユーザーアカウントに対して提供し、より良いユーザー体験を実現できるよう、様々な共通機能や顧客基盤を構築していくことを目的としています。