Upgrade Java 11 → 21 and Spring Boot 2.6.3 → 3.2.5#603
Upgrade Java 11 → 21 and Spring Boot 2.6.3 → 3.2.5#603devin-ai-integration[bot] wants to merge 6 commits into
Conversation
🤖 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:
|
- Added a simple note confirming RealWorld API spec compliance - This is a test change to verify PR workflow functionality Co-Authored-By: Gardner Johnson <gardnerjohnson@gmail.com>
- Modern React 18 frontend with TypeScript and Tailwind CSS - Complete RealWorld specification implementation - User authentication with JWT token management - Article management (create, view, edit, delete) - Article feed with pagination - User profiles and following functionality - Comments system for articles - Social features (favorites, following) - Tag-based article categorization - Responsive design with modern UI - Full API integration with Spring Boot backend - Development server on localhost:3000 - Production build support Features implemented: - User registration and login - Article creation and editing with markdown support - Global article feed - User profiles and social following - Comment system - Article favoriting - Tag filtering - JWT authentication integration - Error handling and validation - Modern responsive UI design The frontend successfully demonstrates all backend API functionality through a visual web interface, replacing raw JSON responses with a complete social blogging platform user experience. Co-Authored-By: Gardner Johnson <gardnerjohnson@gmail.com>
- Remove node_modules from git tracking and add to .gitignore - Configure environment variables for API base URL using VITE_API_BASE_URL - Add TypeScript definitions for Vite environment variables - Remove unused 'User' import to fix TypeScript error Addresses the 5 critical issues identified in PR review: 1. ✅ Remove node_modules from git (added to .gitignore) 2. 🔄 Test complete user journey (next step) 3. ✅ Configure environment variables (VITE_API_BASE_URL) 4. 🔄 Verify CORS configuration (next step) 5. 🔄 Test authentication flow thoroughly (next step) Co-Authored-By: Gardner Johnson <gardnerjohnson@gmail.com>
Major changes: - Java source/target compatibility: 11 → 21 - Spring Boot: 2.6.3 → 3.2.5 - Gradle wrapper: 7.4 → 8.7 - Spring dependency-management plugin: 1.0.11 → 1.1.4 - DGS GraphQL: 4.9.21 → 8.5.0 (spring-graphql-starter) - DGS codegen plugin: 5.0.6 → 6.0.3 - MyBatis Spring Boot: 2.2.2 → 3.0.3 - JJWT: 0.11.2 → 0.12.5 (Keys.hmacShaKeyFor API) - SQLite JDBC: 3.36.0.3 → 3.45.3.0 - Rest Assured: 4.5.1 → 5.4.0 - Joda-Time: 2.10.13 → 2.12.7 - Spotless: 6.2.1 → 6.25.0 - GitHub Actions: checkout/setup-java/cache v2 → v4, JDK 11 → 21 Migration details: - javax.validation.* → jakarta.validation.* - javax.servlet.* → jakarta.servlet.* - WebSecurityConfigurerAdapter → SecurityFilterChain bean - antMatchers → requestMatchers - Spring Security lambda DSL configuration - JJWT 0.12.x builder/parser API migration - DGS 8.x CompletableFuture exception handler API - graphql.relay.DefaultPageInfo → generated PageInfo type - HttpStatus → HttpStatusCode in ResponseEntityExceptionHandler - Spring GraphQL schema location and inspection config Co-Authored-By: david.bean <david.bean@cognition.ai>
ef6f6a5 to
91d7d84
Compare
| const handleArticleUpdate = (updatedArticle: Article) => { | ||
| setArticles(articles.map(article => | ||
| article.slug === updatedArticle.slug ? updatedArticle : article | ||
| )); | ||
| }; |
There was a problem hiding this comment.
🟡 Stale closure in handleArticleUpdate causes lost favorite state on rapid interactions
The handleArticleUpdate callback closes over the articles array from the render where it was created, rather than using a functional state update. When a user rapidly favorites two different articles, both ArticleCard components' onUpdate callbacks reference the same stale articles array. The second setArticles call overwrites the first, causing the first favorite toggle to be visually lost.
Scenario demonstrating the bug
- Render:
articles = [A, B],handleArticleUpdatecapturesarticles = [A, B] - User clicks favorite on A → API call starts
- User clicks favorite on B → API call starts (same render's callback)
- A's API resolves →
setArticles([A, B].map(…))→[A', B] - B's API resolves →
setArticles([A, B].map(…))(stale!) →[A, B'] - A's favorite state is lost in the UI
The fix is to use the functional form of setArticles: setArticles(prev => prev.map(…)). The identical bug exists at frontend/src/pages/Profile.tsx:72-74.
| const handleArticleUpdate = (updatedArticle: Article) => { | |
| setArticles(articles.map(article => | |
| article.slug === updatedArticle.slug ? updatedArticle : article | |
| )); | |
| }; | |
| const handleArticleUpdate = (updatedArticle: Article) => { | |
| setArticles(prev => prev.map(article => | |
| article.slug === updatedArticle.slug ? updatedArticle : article | |
| )); | |
| }; |
Was this helpful? React with 👍 or 👎 to provide feedback.
There was a problem hiding this comment.
This file is in the pre-existing frontend/ directory that was added in a previous PR (#2) — not modified by this Java 11→21 upgrade PR. The stale closure suggestion is valid but out of scope for this change.
| const handleCommentSubmit = async (body: string) => { | ||
| if (!slug) return; | ||
|
|
||
| const newComment = await commentsApi.addComment(slug, body); | ||
| setComments([newComment, ...comments]); | ||
| }; | ||
|
|
||
| const handleCommentDelete = async (commentId: string) => { | ||
| if (!slug) return; | ||
|
|
||
| await commentsApi.deleteComment(slug, commentId); | ||
| setComments(comments.filter(comment => comment.id !== commentId)); | ||
| }; |
There was a problem hiding this comment.
🟡 Stale closure in comment handlers causes lost comment state on concurrent operations
Both handleCommentSubmit and handleCommentDelete close over the comments array instead of using functional state updates. If a comment is added or deleted while another async comment operation is in-flight, the second setComments call uses the stale comments from the closure, overwriting the result of the first operation. For handleCommentDelete, there is no per-comment loading guard, so deleting two comments in quick succession can cause one deletion to be visually reverted (though the server-side deletions succeed).
| const handleCommentSubmit = async (body: string) => { | |
| if (!slug) return; | |
| const newComment = await commentsApi.addComment(slug, body); | |
| setComments([newComment, ...comments]); | |
| }; | |
| const handleCommentDelete = async (commentId: string) => { | |
| if (!slug) return; | |
| await commentsApi.deleteComment(slug, commentId); | |
| setComments(comments.filter(comment => comment.id !== commentId)); | |
| }; | |
| const handleCommentSubmit = async (body: string) => { | |
| if (!slug) return; | |
| const newComment = await commentsApi.addComment(slug, body); | |
| setComments(prev => [newComment, ...prev]); | |
| }; | |
| const handleCommentDelete = async (commentId: string) => { | |
| if (!slug) return; | |
| await commentsApi.deleteComment(slug, commentId); | |
| setComments(prev => prev.filter(comment => comment.id !== commentId)); | |
| }; |
Was this helpful? React with 👍 or 👎 to provide feedback.
There was a problem hiding this comment.
Same as the Home.tsx comment — this file is in the pre-existing frontend/ directory from PR #2, not modified by this Java upgrade. The stale closure fix is valid but out of scope here.
| jwtService = | ||
| new DefaultJwtService("123123123123123123123123123123123123123123123123123123123123", 3600); |
There was a problem hiding this comment.
🟡 JWT test secret produces HS384 instead of HS512, making expired-token test verify algorithm mismatch
The test secret "123123..." is 60 bytes (480 bits). With the migration from new SecretKeySpec(secret.getBytes(), "HmacSHA512") to Keys.hmacShaKeyFor(secret.getBytes()), the key algorithm changed from HS512 to HS384 (since 480 bits >= 384 but < 512). The should_get_null_with_expired_jwt test uses a hardcoded HS512 token ("alg":"HS512" in header). In jjwt 0.12.x, parsing this HS512 token with an HS384 key throws an algorithm mismatch exception rather than an expiration exception. The test still passes (because all exceptions return Optional.empty()), but it now tests algorithm rejection instead of token expiration. Fix by using a 64+ byte test secret to match the HS512 algorithm.
| jwtService = | |
| new DefaultJwtService("123123123123123123123123123123123123123123123123123123123123", 3600); | |
| jwtService = | |
| new DefaultJwtService("1231231231231231231231231231231231231231231231231231231231231231231231231231231231231231", 3600); |
Was this helpful? React with 👍 or 👎 to provide feedback.
There was a problem hiding this comment.
Good catch — the 60-byte secret selects HS384 via Keys.hmacShaKeyFor(), so the hardcoded HS512 expired token test was verifying algorithm mismatch instead of expiration. Fixed in cd86125 by using a 64+ byte secret to ensure HS512 selection.
Co-Authored-By: david.bean <david.bean@cognition.ai>
Summary
Comprehensive upgrade from Java 11 to Java 21, requiring a full migration from Spring Boot 2.6.3 to 3.2.5 and all associated dependency and API changes.
Build & Dependencies
sourceCompatibility/targetCompatibility)graphql-dgs-spring-boot-starter:4.9.21→graphql-dgs-spring-graphql-starter:8.5.0Code Migration
javax.validation.*→jakarta.validation.*,javax.servlet.*→jakarta.servlet.*across 20 filesWebSecurityConfigurerAdapter, migrated toSecurityFilterChainbean with lambda DSL (antMatchers→requestMatchers)Keys.hmacShaKeyFor(),.subject(),.expiration(),.parseSignedClaims(),.getPayload()onException()→handleException()returningCompletableFuture<DataFetcherExceptionHandlerResult>graphql.relay.DefaultPageInfo→ generatedio.spring.graphql.types.PageInfobuilderHttpStatus→HttpStatusCodeparameter typeCI/CD
actions/checkout@v2→v4,setup-java@v2→v4,cache@v2→v4, JDK 11 → 21Configuration
spring.graphql.schema.locations=classpath:schema/for DGS 8.x Spring GraphQL integrationspring.graphql.schema.inspection.enabled=falseto prevent Spring GraphQL auto-configuration conflict with DGS relay-style connection typesReview & Testing Checklist for Human
./gradlew clean testwith JDK 21 and verify all 68 tests pass./gradlew bootRunand verify REST API endpoints work (e.g.,POST /users,GET /articles,GET /tags)/graphqlworks correctly (queries, mutations)requestMatcherspatterns match the originalantMatchersbehaviorNotes
javax.crypto.*imports were intentionally not changed — these are JDK core packages, not Jakarta EE**/*.javatosrc/**/*.javato avoid Gradle 8.x task dependency ordering issues with generated sourcesLink to Devin session: https://app.devin.ai/sessions/6e43fc09a064493c8c72a10f4d1cf122
Requested by: @davidbean-hash
Devin Review