SAP Commerce
Spring Framework 6 and Java 21 Migration for SAP Commerce Cloud

SAP Commerce Cloud’s move to Spring Framework 6 and Java 21 is one of the most impactful technical changes to hit the platform in years. Unlike a typical patch release that you can absorb over a sprint, this migration touches the foundations: the dependency injection framework, the servlet API, the security layer, and the JVM itself. If you have custom extensions (and every real-world SAP Commerce implementation does), you have work to do.
This article is written for developers and architects who need to plan and execute this migration. We will cover what changes, what breaks, how to fix it, and how to test it. This is a technical guide, not a management overview.
Why This Is Happening
SAP Commerce Cloud has historically run on Spring Framework 5.x and Java 17 (or earlier). The broader Java ecosystem has moved forward substantially:
- Spring Framework 5.x reached end of open-source support in August 2024. Security patches for 5.3.x are only available through commercial support. Spring 6 is the current actively developed branch.
- Java 17 is in LTS maintenance, but Java 21 is the new LTS baseline with significant improvements in performance, language features, and runtime capabilities.
- Jakarta EE 9+ replaced the
javaxnamespace withjakarta, affecting the Servlet API, JPA, Bean Validation, and numerous other specifications.
SAP is aligning Commerce Cloud with these ecosystem shifts. The specific version and timeline depends on your Commerce Cloud release train, but the direction is clear: Spring 6 and Java 21 are the new baseline, and custom code that does not comply will not compile.
The Big Three Changes
Before diving into step-by-step migration, understand the three categories of changes that will affect your codebase.
1. Jakarta EE Namespace Migration (javax to jakarta)
This is the most mechanically pervasive change. The Jakarta EE community renamed the package namespace from javax.* to jakarta.* starting with Jakarta EE 9. This affects:
- Servlet API:
javax.servlet.*becomesjakarta.servlet.* - JPA/Persistence:
javax.persistence.*becomesjakarta.persistence.* - Bean Validation:
javax.validation.*becomesjakarta.validation.* - Annotations:
javax.annotation.*becomesjakarta.annotation.* - Mail:
javax.mail.*becomesjakarta.mail.* - WebSocket:
javax.websocket.*becomesjakarta.websocket.* - JSON Processing/Binding:
javax.json.*becomesjakarta.json.*
Importantly, not everything under javax changed. javax.sql.*, javax.crypto.*, javax.net.*, and other packages that are part of the core JDK (not Jakarta EE) remain unchanged.
The sheer number of files affected can be daunting, but the change itself is mechanical. The challenge is finding every occurrence, including in configuration files, XML descriptors, and string literals, not just Java imports.
2. Spring Framework 6 API Changes
Beyond the namespace migration, Spring 6 introduces its own set of breaking changes:
Spring Security overhaul. If you have custom security configurations (and most SAP Commerce implementations do), this is where you will spend significant time. Key changes:
WebSecurityConfigurerAdapteris removed. Security configuration must use the component-basedSecurityFilterChainbean approach.antMatchers(),mvcMatchers()unregexMatchers()are replaced byrequestMatchers().- CSRF configuration changes. The default behavior is stricter, and the configuration API has changed.
- OAuth2 client and resource server configurations have been restructured.
Removed deprecated APIs. Spring 6 removes APIs that were deprecated in Spring 5. If you ignored deprecation warnings (most teams do), those warnings are now compilation errors. Common casualties:
RestTemplateis not removed but is in maintenance mode. Spring recommendsWebClientor the newRestClientfor new code.- Various
org.springframework.web.servlet.mvc.Controllermethods have changed signatures. AsyncRestTemplateis removed entirely. UseWebClientinstead.
HTTP interface changes. Spring 6 requires Servlet 6.0+ (part of the Jakarta EE shift). Filter chains, interceptors, and custom DispatcherServlet configurations may need adjustment.
3. Java 21 Language and Runtime Changes
Java 21 is generally backward-compatible with Java 17 code, but there are breaking changes and new capabilities to be aware of:
Removed APIs and features:
- Security Manager is deprecated for removal. If any of your code or dependencies use
System.setSecurityManager()or security policy files, those need to be reworked. - Several
java.langunjava.utilmethods deprecated in earlier versions are now removed. - The default garbage collector behavior has changed (more on this in the performance section).
New capabilities worth leveraging:
- Virtual threads (Project Loom): Lightweight threads that can dramatically improve throughput for I/O-bound operations. Relevant for integration-heavy commerce platforms.
- Pattern matching for switch: Cleaner code for type-checking and dispatching.
- Record patterns: Useful for DTOs and value objects.
- Sequenced collections: New interfaces (
SequencedCollection,SequencedSet,SequencedMap) that provide uniform access to first/last elements.
You do not have to adopt new Java 21 features during the migration. Focus first on making existing code compile and run correctly. Adopt new features in subsequent iterations.
Step-by-Step Migration Approach
Step 1: Assess Your Custom Code Surface Area
Before changing any code, understand what you are dealing with. Run these assessments across your custom extensions:
Count namespace occurrences:
# Count javax imports that need migration
grep -rn "import javax\.\(servlet\|persistence\|validation\|annotation\|mail\|websocket\|json\)" \
--include="*.java" your-extensions/ | wc -l
# Count Spring Security adapter usage
grep -rn "WebSecurityConfigurerAdapter\|antMatchers\|mvcMatchers\|regexMatchers" \
--include="*.java" your-extensions/ | wc -l
# Count deprecated Spring API usage
grep -rn "AsyncRestTemplate\|org\.springframework\..*deprecated" \
--include="*.java" your-extensions/ | wc -l
Check XML and properties files:
# Check Spring XML configurations for affected classes
grep -rn "javax\.\(servlet\|persistence\|validation\)" \
--include="*.xml" --include="*.properties" your-extensions/
Review third-party dependencies:
Every JAR you bring into your SAP Commerce extensions needs to be compatible with Jakarta EE 10+ and Java 21. Create a dependency inventory and check each one for a compatible version. Common problem areas:
- Older versions of Guava, Apache Commons, Jackson, Gson
- Legacy SOAP client libraries
- Older database drivers
- Testing frameworks (JUnit 4 code may need updates for the new Spring Test context)
Step 2: Update Third-Party Dependencies First
Address your dependency chain before touching your own code. This is counterintuitive (most teams want to start with their own code), but dependency issues will block your compilation anyway, and resolving them first gives you a clean baseline.
For each dependency:
- Check the latest version that supports Jakarta EE 9+ and Java 21
- Review the changelog for breaking changes
- Update the version in your build configuration
- Resolve any immediate compilation issues from the dependency update
Common dependency updates:
| Library | Old Version (typical) | New Version (Jakarta-compatible) |
|---|---|---|
| Jackson | 2.13.x | 2.17.x+ |
| Hibernate Validator | 6.x | 8.x+ |
| EhCache | 2.x | 3.x (or switch to JCache) |
| Apache HttpClient | 4.x | 5.x |
| Lombok | 1.18.24 | 1.18.32+ |
| JUnit | 4.x / 5.8 | 5.10+ |
| Mockito | 4.x | 5.x+ |
Pay special attention to Hibernate. If you have custom JPA/Hibernate code (outside the SAP Commerce Type System), the migration from Hibernate 5 to Hibernate 6 is substantial and involves more than just namespace changes.
Step 3: Perform the Namespace Migration
With dependencies updated, tackle the javax to jakarta namespace change in your custom code.
Automated approach: Use OpenRewrite, which has a recipe specifically for Jakarta EE migration:
# Using OpenRewrite Gradle plugin
./gradlew rewriteRun -Drewrite.activeRecipe=org.openrewrite.java.migrate.jakarta.JavaxMigrationToJakarta
If your SAP Commerce build does not integrate easily with OpenRewrite (many do not due to the custom Ant-based build), you can use IntelliJ IDEA’s migration tool or a targeted find-and-replace approach:
# Manual approach - careful, ordered replacements
# Do these one at a time and verify compilation after each
find your-extensions/ -name "*.java" -exec sed -i '' \
's/javax\.servlet/jakarta.servlet/g' {} +
find your-extensions/ -name "*.java" -exec sed -i '' \
's/javax\.persistence/jakarta.persistence/g' {} +
find your-extensions/ -name "*.java" -exec sed -i '' \
's/javax\.validation/jakarta.validation/g' {} +
find your-extensions/ -name "*.java" -exec sed -i '' \
's/javax\.annotation\.\(PostConstruct\|PreDestroy\|Resource\|Generated\|Priority\)/jakarta.annotation.\1/g' {} +
Do not blindly replace all javax.*. Remember that javax.sql, javax.crypto, javax.net, and JDK-provided packages stay as javax. This is the most common mistake in automated migrations.
Also check:
- web.xml files: Servlet filter and listener class references
- Spring XML configurations: Bean class references that use
javaxtypes - properties files: Any property values that reference
javaxclasses - ImpEx files: Unlikely but check if any ImpEx scripts reference Java types
Step 4: Migrate Spring Security Configuration
If you have custom Spring Security configurations (most SAP Commerce implementations customize authentication, authorization, or CSRF handling), this step requires careful manual work.
Before (Spring Security 5):
@Configuration
@EnableWebSecurity
public class CustomSecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.antMatchers("/api/public/**").permitAll()
.antMatchers("/api/admin/**").hasRole("ADMIN")
.anyRequest().authenticated()
.and()
.csrf()
.csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse());
}
}
After (Spring Security 6):
@Configuration
@EnableWebSecurity
public class CustomSecurityConfig {
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http
.authorizeHttpRequests(auth -> auth
.requestMatchers("/api/public/**").permitAll()
.requestMatchers("/api/admin/**").hasRole("ADMIN")
.anyRequest().authenticated()
)
.csrf(csrf -> csrf
.csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse())
);
return http.build();
}
}
Key changes to note:
- No more extending
WebSecurityConfigurerAdapter - Configuration uses lambda DSL (the
.and()chaining pattern is deprecated) authorizeRequests()becomesauthorizeHttpRequests()antMatchers()becomesrequestMatchers()- CSRF, CORS, and other security configurations use lambda-style configuration
If you have custom AuthenticationProvider, UserDetailsService, or Filter implementations, review them for compatibility. The core contracts are mostly the same, but some edge cases in how filters are ordered and how authentication events are published have changed.
Step 5: Fix Remaining Compilation Errors
After the namespace migration and Spring Security update, attempt a full build. Collect all remaining compilation errors and address them systematically.
Common issues at this stage:
- Removed Spring APIs: Replace with the recommended alternatives (check Spring’s migration guide for each removed class/method)
- Type inference changes in Java 21: Rare, but some generic type inference edge cases behave differently
- Reflection access: Java 21 is stricter about reflective access to internal JDK APIs. If you see
InaccessibleObjectException, you need to add--add-opensJVM flags or (better) stop accessing internal APIs - Classpath vs module path: While SAP Commerce does not fully embrace JPMS modules, some libraries now ship as modules. Classpath conflicts may surface differently
Step 6: Update Build Configuration
Your build system configuration needs updates for Java 21:
JVM flags: Java 21 may require additional --add-opens flags for frameworks that use deep reflection (Hibernate, Spring, serialization libraries). SAP typically documents the required flags for each Commerce release.
Compiler settings: Ensure your build targets Java 21:
# In your build configuration
java.source=21
java.target=21
Gradle/Ant updates: If you use Gradle for any part of your build, ensure you are on Gradle 8.5+ (required for full Java 21 support). For the Ant-based Commerce build, follow SAP’s documentation for the specific version requirements.
Test frameworks: Ensure your test runner and assertion libraries are compatible. JUnit 5.10+ and Mockito 5.x+ are the safe choices for Java 21.
Step 7: Testing Strategy
Migration testing has three layers, and you need all of them.
Layer 1: Compilation and Unit Tests
The first gate is simply getting the code to compile and unit tests to pass. Expect some test failures due to:
- Mocking framework changes (Mockito 5 has stricter defaults about mocking final classes)
- Spring Test context changes (how test application contexts are initialized)
- Assertion library compatibility
Fix these methodically. Do not skip failing tests – they exist for a reason.
Layer 2: Integration Tests
SAP Commerce-specific integration tests (those extending ServicelayerTransactionalTest or similar base classes) verify that your code works within the Commerce runtime. These tests are critical because they exercise the actual Spring context, type system, and service layer.
Run these in a CCv2 development environment or a local Commerce instance running on Java 21. Pay special attention to:
- Service layer injection (Spring context must resolve all beans correctly)
- Interceptor chains (model interceptors, event listeners)
- Cron job execution
- ImpEx import/export
Layer 3: End-to-End Functional Tests
Once unit and integration tests pass, run your full regression suite. Key areas to validate:
- Checkout flow (the most integration-heavy path)
- Backoffice operations (SmartEdit, Product Cockpit, etc.)
- API endpoints (OCC REST API responses)
- Search and indexing (Solr integration)
- Background processes (order processing, data sync jobs)
Step 8: Performance Validation
Java 21 brings performance improvements, but your specific workload may behave differently. Validate before go-live.
Garbage Collection: Java 21’s default GC (G1) has received significant improvements. If you were using G1 on Java 17, you should see modest throughput improvements. If you were using ZGC or Shenandoah for low-latency requirements, both are more mature in Java 21.
Review your GC configuration:
# Common GC flags to review and adjust for Java 21
-XX:+UseG1GC
-XX:MaxGCPauseMillis=200
-XX:G1HeapRegionSize=16m
-XX:InitiatingHeapOccupancyPercent=45
Java 21’s G1 improvements may allow you to relax some of these tuning parameters. Run load tests with your current settings first, then experiment.
Virtual Threads: Java 21’s virtual threads (from Project Loom) are a significant opportunity for I/O-bound workloads, which describes most e-commerce platforms. A typical product page load involves database queries, cache lookups, Solr searches, and potentially external API calls, all I/O operations that block platform threads.
Virtual threads allow you to use Executors.newVirtualThreadPerTaskExecutor() instead of traditional thread pools. The JVM handles scheduling with dramatically lower overhead per thread. For integration-heavy flows (order processing, inventory sync), this can significantly improve throughput without increasing hardware.
However, do not adopt virtual threads during the initial migration. Get everything working on platform threads first. Virtual thread adoption is an optimization to pursue after the migration is stable.
Startup time: Java 21 with Spring 6 may have different startup characteristics. Measure cold start and warm-up times in your CCv2 environments, as this affects deployment and scaling behavior.
Dependency Audit Checklist
Run through this checklist for every third-party dependency in your custom extensions:
- [ ] Latest version supports Jakarta EE 9+ namespace?
- [ ] Latest version supports Java 21?
- [ ] Breaking changes between your current version and the Jakarta-compatible version documented?
- [ ] Transitive dependencies also compatible? (a common trap: your library is compatible, but it pulls in an older version of something that is not)
- [ ] License unchanged in the new version?
- [ ] If no compatible version exists: is there an alternative library? Can you inline the functionality?
Timeline and Effort Estimates
Based on our experience with SAP Commerce implementations of varying complexity:
| Codebase Size | Custom Extensions | Estimated Effort | Calendar Time |
|---|---|---|---|
| Small | < 10 extensions, < 50K LoC | 2-4 developer-weeks | 4-6 weeks |
| Medium | 10-25 extensions, 50-150K LoC | 6-12 developer-weeks | 8-14 weeks |
| Large | 25+ extensions, 150K+ LoC | 15-25 developer-weeks | 14-22 weeks |
These estimates cover code migration, testing, and stabilization. They assume developers familiar with both SAP Commerce and modern Java/Spring. If your team needs to learn Spring 6 and Java 21 concepts during the migration, add 30-50% for the learning curve.
Critical path items that often determine timeline:
- Third-party dependency compatibility (can be a blocker if a critical library lacks a compatible version)
- Spring Security reconfiguration (always takes longer than expected)
- Integration testing in CCv2 environments (environment availability and deployment cycle times are bottlenecks)
Common Pitfalls
Pitfall 1: Bulk find-and-replace without verification. Replacing javax with jakarta globally will break JDK classes. Always use targeted replacements for specific Jakarta EE packages only.
Pitfall 2: Ignoring transitive dependencies. Your code compiles, but a library pulls in an old javax.servlet-api JAR that conflicts at runtime. Use dependency analysis tools (gradle dependencies or mvn dependency:tree) to find and exclude conflicting transitives.
Pitfall 3: Skipping the Spring Security migration. “We’ll do it later.” Spring Security changes are deeply integrated with the platform’s authentication and authorization. Postponing this creates a ticking time bomb.
Pitfall 4: Not testing with production-like data. The migration may work perfectly with a test dataset and fail with production data volumes. Particularly relevant for JPA/Hibernate changes where query behavior can differ subtly.
Pitfall 5: Underestimating SAP-specific customization points. SAP Commerce has its own patterns for filters, interceptors, and event handlers that layer on top of Spring. These need individual attention – they do not automatically migrate with generic Spring migration recipes.
Closing Notes
The Spring 6 and Java 21 migration for SAP Commerce is substantial but manageable. It is primarily a mechanical exercise (namespace changes, API updates) combined with targeted architectural work (Spring Security, build configuration). The code changes are well-understood, and the ecosystem provides good tooling for most of the heavy lifting.
The key is to approach it methodically: dependencies first, then namespace migration, then Spring-specific changes, then thorough testing at every layer. Resist the temptation to combine this migration with feature work or other architectural changes. Do one thing at a time, and do it well.
If your team needs guidance on planning or executing this migration, our SAP Commerce consulting services include technical architecture support for exactly this type of platform evolution. With over 12 years of SAP Commerce experience, we can help you navigate the migration efficiently and avoid the pitfalls that cost other teams weeks of rework.



