Skip to content

App Modernization: Java 11 → 21, Spring Boot 2.6.3 → 3.4.4#148

Open
devin-ai-integration[bot] wants to merge 8 commits into
mainfrom
devin/1781526890-app-modernization-java21-spring-boot-3
Open

App Modernization: Java 11 → 21, Spring Boot 2.6.3 → 3.4.4#148
devin-ai-integration[bot] wants to merge 8 commits into
mainfrom
devin/1781526890-app-modernization-java21-spring-boot-3

Conversation

@devin-ai-integration

@devin-ai-integration devin-ai-integration Bot commented Jun 15, 2026

Copy link
Copy Markdown

Summary

Major version upgrade from Java 11 / Spring Boot 2.6.3 to Java 21 / Spring Boot 3.4.4, done in 6 verified layers. Each layer committed independently and verified against ./gradlew clean test spotlessCheck. All 68 tests pass on Java 21.

Why 3.4.4 instead of 3.5.x: Spring Boot 3.5.0's spring-graphql:1.4.0 requires java-dataloader with DataLoaderOptions$Builder, which conflicts with DGS 9.1.3's strictly [3.3.0] constraint. 3.4.4 is the latest Boot 3 release compatible with the DGS 9.x ecosystem.

Layer 1 — Build Config

Gradle 7.4→8.12, sourceCompatibility/targetCompatibility 11→21, Spring Boot plugin 2.6.3→3.4.4, dependency-management 1.0.11→1.1.7, Spotless 6.2.1→6.25.0, JaCoCo 0.8.12. Removed explicit javax.validation:validation-api (Boot 3 provides jakarta.validation-api transitively). Spotless target narrowed to src/**/*.java to avoid Gradle 8 implicit-dependency warnings with generated sources.

Layer 2 — Namespace Migration

javax.validation.*jakarta.validation.*, javax.servlet.*jakarta.servlet.* across 19 source files.

Layer 3 — Security Config

// before: extends WebSecurityConfigurerAdapter
// after:
@Bean SecurityFilterChain securityFilterChain(HttpSecurity http) {
  http.csrf(csrf -> csrf.disable())
      .authorizeHttpRequests(auth -> auth
          .requestMatchers(HttpMethod.OPTIONS).permitAll()  // was antMatchers
          .requestMatchers("/graphql").permitAll()
          ...);
}

CustomizeExceptionHandler.handleMethodArgumentNotValid signature updated for HttpStatusCode (Boot 3).

Layer 4 — Infrastructure

  • jjwt 0.11→0.12: Jwts.builder().subject().expiration().signWith(key), Jwts.parser().verifyWith(key).build().parseSignedClaims(). Key padded to 64 bytes for Keys.hmacShaKeyFor() minimum.
  • joda-time→java.time: DateTimeOffsetDateTime across domain entities (Article, Comment), data classes, query services. Custom DateTimeHandler (MyBatis TypeHandler) rewritten for OffsetDateTime. DateTimeCursor uses Instant/OffsetDateTime. JacksonCustomizations serializer uses DateTimeFormatter.ISO_OFFSET_DATE_TIME.
  • MyBatis 2.2→3.0.4: starter aligned with Boot 3.

Layer 5 — DGS GraphQL 4.x → 9.x

graphql.relay.DefaultPageInfo→DGS-generated io.spring.graphql.types.PageInfo (builder API). DataFetcherExceptionHandler.onExceptionhandleException returning CompletableFuture<DataFetcherExceptionHandlerResult>. Platform BOM graphql-dgs-platform-dependencies:9.1.3 + eachDependency resolution strategy to align graphql-java:22.3 / java-dataloader:3.3.0 with DGS codegen 5.12.4.

Layer 6 — Test Dependencies

REST-Assured 4.5→5.5, Selenium WebDriverWait(driver, long)WebDriverWait(driver, Duration), httpclient5 version managed by Boot (was explicit 5.2.1, conflicted with Boot-managed version causing TlsSocketStrategy ClassNotFoundException). Test fixtures updated for OffsetDateTime.

Verification

./gradlew clean test spotlessCheck -x jacocoTestCoverageVerification → BUILD SUCCESSFUL
68 tests passed, 0 failed

Note: JaCoCo coverage verification (80% threshold) was already failing on the baseline main branch (0% coverage ratio due to class mismatch) — not a regression introduced by this PR.

Link to Devin session: https://partner-workshops.devinenterprise.com/sessions/e99d31e33ea14a2ab366ca4338bfb7d4
Requested by: @bsmitches


Open in Devin Review

…ndency versions

- Spring Boot 2.6.3 -> 3.4.4
- Gradle 7.4 -> 8.12
- Java 11 -> 21
- DGS codegen 5.0.6 -> 5.12.4, DGS runtime 4.9.21 -> 9.1.3
- jjwt 0.11.2 -> 0.12.6
- REST-Assured 4.5.1 -> 5.5.0
- MyBatis starter 2.2.2 -> 3.0.4
- Spotless 6.2.1 -> 6.25.0
- JaCoCo toolVersion 0.8.12
- Remove joda-time dependency
- Remove explicit javax.validation:validation-api
- javax.validation -> jakarta.validation (20 files)
- javax.servlet -> jakarta.servlet (JwtTokenFilter)
- Updated GraphQL exception handler for DGS 9.x handleException API
- WebSecurityConfigurerAdapter -> SecurityFilterChain bean
- Lambda DSL for csrf, cors, sessionManagement, authorizeHttpRequests
- antMatchers -> requestMatchers
- authorizeRequests -> authorizeHttpRequests
- HttpStatus -> HttpStatusCode in handleMethodArgumentNotValid override
- joda-time DateTime -> java.time.OffsetDateTime (all domain + data classes)
- DateTimeHandler: MyBatis TypeHandler rewritten for OffsetDateTime
- DateTimeCursor: use Instant/OffsetDateTime for cursor pagination
- JacksonCustomizations: OffsetDateTimeSerializer with ISO format
- DefaultJwtService: jjwt 0.12 API (subject/expiration/signWith/parseSignedClaims)
- Keys.hmacShaKeyFor with padding for minimum key size requirement
- Replace graphql.relay.DefaultPageInfo with DGS-generated PageInfo type
- Remove graphql.relay.DefaultConnectionCursor usage
- ISODateTimeFormat (joda) -> DateTimeFormatter.ISO_OFFSET_DATE_TIME (java.time)
- DataFetcherExceptionHandler.onException -> handleException (CompletableFuture)
- joda DateTime -> java.time.OffsetDateTime in all test fixtures
- ISODateTimeFormat -> DateTimeFormatter in ArticleApiTest assertions
- REST-Assured 4.x -> 5.x (compatible, no API changes needed)
- Selenium WebDriverWait: long timeout -> Duration (Selenium 4 API)
- Let Spring Boot manage httpclient5 version to avoid classpath conflicts
@devin-ai-integration

Copy link
Copy Markdown
Author

🤖 Devin AI Engineer

I'll be helping with this pull request! Here's what you should know:

✅ I will automatically:

  • Address comments on this PR. Add '(aside)' to your comment to have me ignore it.
  • Look at CI failures and help fix them

Note: I can only respond to comments from users who have write access to this repository.

⚙️ Control Options:

  • Disable automatic comment, CI, and merge conflict monitoring

Update setup-java from JDK 11 to JDK 21 to match the upgraded project.

@devin-ai-integration devin-ai-integration Bot left a comment

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Devin Review found 2 potential issues.

Open in Devin Review

Comment thread src/main/java/io/spring/infrastructure/service/DefaultJwtService.java Outdated
Comment thread build.gradle
implementation 'org.mybatis.spring.boot:mybatis-spring-boot-starter:3.0.4'
implementation(platform('com.netflix.graphql.dgs:graphql-dgs-platform-dependencies:9.1.3'))
implementation 'com.netflix.graphql.dgs:graphql-dgs-spring-graphql-starter'
implementation 'org.flywaydb:flyway-core'

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🚩 Flyway + SQLite may require additional dependency with Flyway 10.x

Spring Boot 3.4.4 manages Flyway 10.x, which modularized database support. The build only declares implementation 'org.flywaydb:flyway-core' (build.gradle:77), but the application uses SQLite (application.properties:1jdbc:sqlite:dev.db). In Flyway 10+, database-specific support was extracted into separate modules. SQLite may require org.flywaydb:flyway-community-db-support or a similar module. Without it, the application could fail at startup with a "No database found to handle jdbc:sqlite" error. I could not verify the exact Flyway version managed by Spring Boot 3.4.4 or whether SQLite is still bundled in core, so this needs investigation.

Open in Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Investigated — this is not an issue for this project. Spring Boot 3.4.4 manages Flyway 10.22.x, and the Flyway 10.x modularization extracted support for commercial databases into separate modules. SQLite support remains in flyway-core for community databases. All 68 tests pass including the ones that exercise Flyway migrations against the SQLite dev.db, confirming that the current flyway-core dependency is sufficient.

…ding

Replace zero-byte padding with a minimum-length validation (32 bytes for
HMAC-SHA). Keys.hmacShaKeyFor() auto-selects HS256/HS384/HS512 based on
key length. Short secrets now throw IllegalArgumentException at startup
rather than silently weakening key material.

Addresses Devin Review feedback.

@devin-ai-integration devin-ai-integration Bot left a comment

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Devin Review found 1 new potential issue.

Open in Devin Review

@MappedTypes(OffsetDateTime.class)
public class DateTimeHandler implements TypeHandler<OffsetDateTime> {

private static final Calendar UTC_CALENDAR = Calendar.getInstance(TimeZone.getTimeZone("UTC"));

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🚩 Static mutable Calendar instance shared across threads in DateTimeHandler

The UTC_CALENDAR at line 19 is a static final Calendar instance shared by all threads. Calendar is mutable and not thread-safe — JDBC drivers may call mutating methods like set() or clear() on it during getTimestamp/setTimestamp calls, potentially causing race conditions under concurrent requests. This is a pre-existing issue (not introduced by this PR), but worth noting since it wasn't addressed during the migration. A safer approach would be to create a new Calendar instance per call, or use ThreadLocal<Calendar>.

Open in Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agreed this is a valid thread-safety concern, but as noted it's pre-existing behavior carried over from the original joda-time DateTimeHandler — not a regression from this migration. Fixing it would be a separate improvement outside the scope of this upgrade PR. A follow-up could either create a new Calendar per call or switch to Timestamp.from(instant) / Timestamp.valueOf(localDateTime) which avoids Calendar entirely.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant