feature: migrate to Java 17 and Spring Boot 3.3 - #1053
ginawang-cog wants to merge 8 commits into
Conversation
Co-Authored-By: Gina Wang <ginawang1021@gmail.com>
🤖 Devin AI EngineerI'll be helping with this pull request! Here's what you should know: ✅ I will automatically:
Note: I can only respond to comments from users who have write access to this repository. ⚙️ Control Options:
|
| } | ||
|
|
||
| repositories { | ||
| maven { url = uri('https://maven-central.storage-download.googleapis.com/maven2') } |
There was a problem hiding this comment.
🔍 Google Maven mirror ahead of mavenCentral
A Google-hosted Central mirror sits before mavenCentral() in repositories, added to work around HTTP 429 in one environment. Committing it makes every consumer of the build resolve artifacts through that third-party endpoint first.
Was this helpful? React with 👍 or 👎 to provide feedback.
There was a problem hiding this comment.
Agreed this is the questionable part of the diff. The mirror is there because Maven Central answers HTTP 429 from our build environment, which Gradle treats as a hard resolution failure rather than falling through to the next repository — so simply putting mavenCentral() first would not give a working fallback. It is a read-only Google-hosted Central mirror, but it is still a third-party endpoint in the resolution path for every consumer.
Leaving it in for now so the parallel migration branches can resolve, and flagging it to the repo owner: the alternative is to drop it from build.gradle and put it in a per-machine Gradle init script instead. Happy to do that before this lands.
… Spring 6 APIs Co-Authored-By: Gina Wang <ginawang1021@gmail.com>
Co-Authored-By: Gina Wang <ginawang1021@gmail.com>
Co-Authored-By: Gina Wang <ginawang1021@gmail.com>
Co-Authored-By: Gina Wang <ginawang1021@gmail.com>
Co-Authored-By: Gina Wang <ginawang1021@gmail.com>
| signatureAlgorithm = Jwts.SIG.HS512; | ||
| this.signingKey = new SecretKeySpec(secret.getBytes(), JCA_NAME); | ||
| } |
There was a problem hiding this comment.
📝 Info: JWT secret now must be at least 64 bytes
jjwt 0.12.6 rejects HS512 keys under 512 bits at signing time, which is why the test secret grew from 60 to 64 chars. The bundled jwt.secret is 86 chars so it works, but any deployment overriding it with a shorter value will now fail at token creation where 0.11.2 tolerated it. See DefaultJwtService.
Was this helpful? React with 👍 or 👎 to provide feedback.
There was a problem hiding this comment.
Correct, and worth flagging in release notes. Confirmed the bundled jwt.secret in application.properties is 86 chars (688 bits) and signs/round-trips fine, but jjwt 0.12 enforces the RFC 7518 512-bit minimum for HS512 at signing time, so any deployment overriding jwt.secret with fewer than 64 ASCII chars will now throw WeakKeyException on token creation instead of silently signing with a short key (which is what 0.11.2 did). That's the reason the test fixture went 60 -> 64 chars — the fixture was the only thing in the repo below the bound.
Leaving the behavior as-is deliberately: the old tolerance was the bug, and adding a fallback that pads or hashes a short secret would change token signatures for anyone currently running a short one. Flagging rather than fixing.
| private PageInfo buildArticlePageInfo(CursorPager<ArticleData> articles) { | ||
| return PageInfo.newBuilder() | ||
| .startCursor( | ||
| articles.getStartCursor() == null ? null : articles.getStartCursor().toString()) | ||
| .endCursor(articles.getEndCursor() == null ? null : articles.getEndCursor().toString()) | ||
| .hasPreviousPage(articles.hasPrevious()) | ||
| .hasNextPage(articles.hasNext()) | ||
| .build(); |
There was a problem hiding this comment.
📝 Info: Relay PageInfo swap matches schema fields
Codegen 6.3.0 emits io.spring.graphql.types.PageInfo rather than the relay type, so the rewritten builders map startCursor/endCursor/hasNextPage/hasPreviousPage, matching the PageInfo definition in the schema. Generated sources under build/ are not checked in, so the builder signature could not be verified directly.
Was this helpful? React with 👍 or 👎 to provide feedback.
There was a problem hiding this comment.
Verified the part you couldn't see. The generated build/generated/sources/dgs-codegen/io/spring/graphql/types/PageInfo.java exposes exactly Builder.startCursor(String), endCursor(String), hasNextPage(boolean), hasPreviousPage(boolean), matching the schema's type PageInfo { endCursor: String, hasNextPage: Boolean!, hasPreviousPage: Boolean!, startCursor: String }. The switch was forced rather than stylistic: ArticlesConnection.Builder.pageInfo now takes types.PageInfo, so graphql.relay.DefaultPageInfo no longer type-checks there.
Serialized shape is unchanged — the cursors are the same DateTimeCursor.toString() strings, just no longer wrapped in DefaultConnectionCursor, and the null-cursor cases still emit null.
Summary
Full migration of the RealWorld app from Java 11 / Spring Boot 2.6.3 to Java 17 / Spring Boot 3.3.13. The public Conduit REST contract, the GraphQL schema and
/graphql+/graphiqlendpoints, and the JWT token format (HS512,Authorization: Token <jwt>) are all unchanged../gradlew clean buildis green on JDK 17 (68 tests).This branch was assembled from three workstreams (the two source-migration PRs, #1054 and #1055, merged into this branch).
Build (WS-A)
2.6.3→3.3.13, dependency-management1.0.11→1.1.7, Gradle wrapper7.4→8.10.2,sourceCompatibility/targetCompatibility = 11→ a Java 17toolchain.2.2.2→3.0.4, DGS codegen5.0.6→6.3.0, Spotless6.2.1→6.25.0, jjwt0.11.2→0.12.6, joda-time and sqlite-jdbc bumped.graphql-dgs-spring-boot-starter:4.9.21→graphql-dgs-spring-graphql-starter:9.2.2.4.5.1; the Boot BOM supplies5.4.0.fileTree('src')instead of the wholerootDir— under Gradle 8 the root-wide tree madespotlessJavaan undeclared consumer ofbuild/generated, failingclean build.settings.gradlegained apluginManagementblock, and a Google-hosted Maven Central mirror is listed ahead ofmavenCentral()because Central rate-limited (HTTP 429) this environment. See the note below — this is the one thing worth a reviewer decision.Jakarta + Spring 6 (WS-B, #1054)
javax.validation.*→jakarta.validation.*acrossio.spring.api.*/io.spring.application.*, andjavax.servlet.*→jakarta.servlet.*inJwtTokenFilter.ResponseEntityExceptionHandlerhook signature, so the@Overridehad to follow; the body it builds is untouched, so the 422{"errors": {...}}payload is byte-for-byte identical:Spring Security 6 / GraphQL / jjwt (WS-C, #1055)
WebSecurityConfigurerAdapteris gone in Security 6, soWebSecurityConfignow exposes aSecurityFilterChainbean using the lambda DSL andrequestMatchers. Every rule is carried over in the same order (OPTIONS,/graphiql,/graphql,GET /articles/feedauthenticated,POST /users|/users/login,GET /articles/**|/profiles/**|/tags, thenanyRequest().authenticated()), along with csrf-disabled, CORS, theHttpStatusEntryPoint(UNAUTHORIZED), stateless sessions, andjwtTokenFilterbeforeUsernamePasswordAuthenticationFilter.DataFetcherExceptionHandler.onException(params)→handleException(params)returningCompletableFuture<DataFetcherExceptionHandlerResult>. Each existingreturn resultis wrapped inCompletableFuture.completedFuture(...), so the emitted GraphQL errors are unchanged.DefaultPageInfoto the DGS-generated connection types (the newer generatedArticlesConnection.pageInfotakes the generatedtypes.PageInfo), sobuildArticlePageInfo/buildCommentPageInfonow buildtypes.PageInfodirectly. Same four fields, same values — cursors are plain strings instead ofDefaultConnectionCursorwrappers.DefaultJwtServicemoved to the jjwt 0.12 API (Jwts.SIG.HS512,subject/expiration,Jwts.parser().verifyWith(key).parseSignedClaims(token).getPayload()).MacAlgorithmno longer exposesgetJcaName(), so theSecretKeySpecalgorithm is the literal"HmacSHA512". Verified at runtime that the productionjwt.secretstill produces{"alg":"HS512"}and round-trips.DefaultJwtServiceTest's 480-bit secret is below the key length jjwt 0.12 now enforces for HS512, so it was lengthened to 64 chars. No assertions were modified anywhere.spring.graphql.graphiql.enabled=true; the spring-graphql starter disables GraphiQL by default, andspring.graphql.path/graphiql.pathalready default to the existing/graphqland/graphiql.Notes for reviewers
build.gradleis a workaround for this build environment, not a project decision. Central returned HTTP 429 here, sohttps://maven-central.storage-download.googleapis.com/maven2was added ahead ofmavenCentral(). It makes every consumer of this build resolve through a third-party mirror first; happy to drop it if CI can reach Central directly.master: 99 unique findings here vs. 193 onmaster, and 83 of this branch's 99 are also present onmaster. The 16 findings unique to this branch are all transitive (spring-graphql 1.3.5, tomcat-embed-core 10.1.42, spring-hateoas 2.3.4, spring-security-crypto/core 6.3.10, jackson-databind 2.17.3, spring-boot-autoconfigure 3.3.13) and none of them have a fix reachable from Boot 3.3.x / DGS 9.x — Snyk's remediation paths point at Boot 3.5.x/4.0.x, Spring 7, Spring Security 7, and DGS 10. Clearing them means leaving the requested 3.3 line.Devin-Org: engineering
Link to Devin session: https://app.devin.ai/sessions/f39f2107da574652ab427738a2872571
Open in Devin Desktop: https://app.devin.ai/desktop/session/f39f2107da574652ab427738a2872571?variant=devin
Requested by: @ginawang-cog
Devin Review