| name | jakarta-java25-migration |
| description | Invoke this skill to migrate Java 8/11 to Java 25 and javax.* to jakarta.* namespaces. Detects Java version < 25 in pom.xml or javax.* imports in source files. Foundation skill - invoke before sdk-replacement. |
| disable-model-invocation | false |
| allowed-tools | Read, Edit, Write, Bash, Grep, Glob |
Jakarta EE 10 and Java 25 Migration
Migrate your Neo Java application from Java 8/11 and Java EE to Java 25 and Jakarta EE 10.
Purpose
Upgrade the Java runtime and migrate from javax.* namespaces to jakarta.* namespaces for forward compatibility with modern application servers and Cloud Foundry buildpacks.
Detection
This skill applies if any of these patterns are found:
In pom.xml
<maven.compiler.source>1.8</maven.compiler.source>
<maven.compiler.target>1.8</maven.compiler.target>
<properties>
<java.version>11</java.version>
</properties>
In Java source files
import javax.servlet.*;
import javax.persistence.*;
import javax.annotation.*;
import javax.mail.*;
import javax.ejb.*;
Prerequisites
- None (this is typically the first migration step)
- Maven 3.6+ installed
- Java 25+ available on PATH
Transformation Steps
Step 0: Create Migration Copy
Before making any changes, create a copy of the application directory as a sibling folder. All migration work is done on this copy — the original stays untouched.
APP_DIR=$(pwd)
APP_NAME=$(basename "$APP_DIR")
COPY_DIR="$(dirname "$APP_DIR")/${APP_NAME}-cf-migration"
if [ -d "$COPY_DIR" ]; then
echo "Migration copy already exists at $COPY_DIR — using it."
else
cp -r "$APP_DIR" "$COPY_DIR"
echo "Created migration copy at $COPY_DIR"
fi
cd "$COPY_DIR"
Now that we are inside the copy, create the .migration/ directory and save the config there:
mkdir -p .migration
Save the paths to .migration/cf-migration-config.json (create or update the file):
{
"sourceAppDir": "<original APP_DIR>",
"migrationAppDir": "<COPY_DIR>"
}
All subsequent steps in this skill and all downstream skills (sdk-replacement, authentication-xsuaa, mta-descriptor, etc.) must operate inside $COPY_DIR. The .migration/ directory is inside the copy, not the original.
Step 1: Update Java Version in pom.xml
Before:
<properties>
<maven.compiler.source>1.8</maven.compiler.source>
<maven.compiler.target>1.8</maven.compiler.target>
</properties>
After:
<properties>
<maven.compiler.source>25</maven.compiler.source>
<maven.compiler.target>25</maven.compiler.target>
</properties>
Step 2: Run OpenRewrite Migration
Execute the OpenRewrite recipes to automatically migrate code:
mvn -U org.openrewrite.maven:rewrite-maven-plugin:run \
-Drewrite.recipeArtifactCoordinates=org.openrewrite.recipe:rewrite-migrate-java:RELEASE \
-Drewrite.activeRecipes=org.openrewrite.java.migrate.UpgradeToJava25 \
-Drewrite.activeRecipes=org.openrewrite.java.migrate.jakarta.JakartaEE10 \
-Drewrite.exportDatatables=true
Note:
- Make sure you have Java SE 25 and the latest Maven version installed
- The recipe artifact coordinates ensure the latest migration recipes are downloaded
This command will:
- Update Java language features to Java 25
- Replace
javax.* imports with jakarta.*
- Update deprecated API usages
- Migrate web.xml namespace declarations
Step 3: Handle OpenCMIS Caveat
Important: The org.apache.chemistry.opencmis libraries have NOT been migrated to Jakarta EE. If your application uses OpenCMIS (Document Management), you must exclude it from the Jakarta migration.
Check for OpenCMIS usage:
grep -r "opencmis" pom.xml
grep -r "org.apache.chemistry" src/main/java/
If OpenCMIS is used, add these specific exclusions:
<dependency>
<groupId>org.apache.chemistry.opencmis</groupId>
<artifactId>chemistry-opencmis-client-impl</artifactId>
<version>${opencmis.version}</version>
<exclusions>
<exclusion>
<groupId>org.apache.cxf</groupId>
<artifactId>cxf-rt-frontend-jaxws</artifactId>
</exclusion>
<exclusion>
<groupId>org.apache.cxf</groupId>
<artifactId>cxf-rt-transports-http</artifactId>
</exclusion>
<exclusion>
<groupId>org.apache.cxf</groupId>
<artifactId>cxf-rt-ws-policy</artifactId>
</exclusion>
</exclusions>
</dependency>
Note: Add the excluded libraries as separate dependencies using their latest versions, if applicable.
Step 4: Update web.xml Namespace (if not auto-migrated)
Before:
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee
http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd"
version="4.0">
After:
<web-app xmlns="https://jakarta.ee/xml/ns/jakartaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="https://jakarta.ee/xml/ns/jakartaee
https://jakarta.ee/xml/ns/jakartaee/web-app_6_0.xsd"
version="6.0">
Step 5: Common Import Migrations
| Before (javax.*) | After (jakarta.*) |
|---|
javax.servlet.* | jakarta.servlet.* |
javax.servlet.http.* | jakarta.servlet.http.* |
javax.persistence.* | jakarta.persistence.* |
javax.annotation.* | jakarta.annotation.* |
javax.ejb.* | jakarta.ejb.* |
javax.mail.* | jakarta.mail.* |
javax.ws.rs.* | jakarta.ws.rs.* |
javax.json.* | jakarta.json.* |
javax.validation.* | jakarta.validation.* |
com.fasterxml.jackson.jaxrs.* | com.fasterxml.jackson.jakarta.rs.* |
Important — Jackson JAX-RS providers: The Jackson JAX-RS provider artifact changed from jackson-jaxrs-json-provider (javax) to jackson-jakarta-rs-json-provider (jakarta). In addition to the package rename, the class JacksonJaxbJsonProvider was renamed to JacksonXmlBindJsonProvider. The simpler JacksonJsonProvider (which supports JAXB annotations too) is the recommended replacement. OpenRewrite will update Java import statements, but it does not update class references in web.xml. If your web.xml configures JAX-RS providers via <init-param>, you must update those manually.
Update web.xml JAX-RS Provider References
Detect:
grep -r "com.fasterxml.jackson.jaxrs" --include="*.xml" src/main/webapp/
If this returns results, update the class name:
Before:
<init-param>
<param-name>jaxrs.providers</param-name>
<param-value>
com.fasterxml.jackson.jaxrs.json.JacksonJaxbJsonProvider
</param-value>
</init-param>
After:
<init-param>
<param-name>jaxrs.providers</param-name>
<param-value>
com.fasterxml.jackson.jakarta.rs.json.JacksonJsonProvider
</param-value>
</init-param>
Note: The available classes in the Jakarta artifact are JacksonJsonProvider (recommended) and JacksonXmlBindJsonProvider. The old name JacksonJaxbJsonProvider does not exist in the Jakarta artifact — using it will cause ClassNotFoundException at runtime even though the JAR is in the WAR.
Also update the Maven dependency:
Before:
<dependency>
<groupId>com.fasterxml.jackson.jaxrs</groupId>
<artifactId>jackson-jaxrs-json-provider</artifactId>
</dependency>
After:
<dependency>
<groupId>com.fasterxml.jackson.jakarta.rs</groupId>
<artifactId>jackson-jakarta-rs-json-provider</artifactId>
</dependency>
Step 6: Migrate CXF Swagger 2 to OpenAPI v3 (Conditional)
When migrating to Jakarta EE 10, CXF must be upgraded from 3.x to 4.x (CXF 3.x uses javax.ws.rs.*, which is incompatible). In CXF 4.x, the Swagger 2 module (cxf-rt-rs-service-description-swagger) was removed and replaced by the OpenAPI v3 module.
This step is conditional. Only apply if the detection check below returns results.
Detect
find . -name "pom.xml" -not -path "*/target/*" -exec grep -l "cxf-rt-rs-service-description-swagger" {} \;
grep -r "io.swagger.annotations" --include="*.java" .
grep -r "Swagger2Feature" --include="*.java" --include="*.xml" .
If any of the above commands return results, apply the transformations below.
6a: Replace Maven Dependency
Before:
<dependency>
<groupId>org.apache.cxf</groupId>
<artifactId>cxf-rt-rs-service-description-swagger</artifactId>
<version>${cxf.version}</version>
</dependency>
After:
<dependency>
<groupId>org.apache.cxf</groupId>
<artifactId>cxf-rt-rs-service-description-openapi-v3</artifactId>
<version>${cxf.version}</version>
</dependency>
Note: Ensure ${cxf.version} is 4.x or later (e.g., 4.0.5). CXF 3.x does not have the openapi-v3 module.
6b: Replace Annotations
| Before (Swagger 2) | After (OpenAPI v3) |
|---|
import io.swagger.annotations.Api | import io.swagger.v3.oas.annotations.tags.Tag |
import io.swagger.annotations.ApiOperation | import io.swagger.v3.oas.annotations.Operation |
import io.swagger.annotations.ApiParam | import io.swagger.v3.oas.annotations.Parameter |
import io.swagger.annotations.ApiResponse | import io.swagger.v3.oas.annotations.responses.ApiResponse |
@Api(value = "...") | @Tag(name = "...") |
@ApiOperation(value = "...") | @Operation(summary = "...") |
@ApiParam(value = "...") | @Parameter(description = "...") |
6c: Replace Feature Class (if used programmatically)
If CXF features are registered in code (e.g., ServiceRegistry or Application subclass):
Before:
import org.apache.cxf.jaxrs.swagger.Swagger2Feature;
Swagger2Feature feature = new Swagger2Feature();
feature.setBasePath("/api");
After:
import org.apache.cxf.jaxrs.openapi.OpenApiFeature;
OpenApiFeature feature = new OpenApiFeature();
6d: Fix jakarta.xml.ws-api Scope for CXF OpenApiFeature
CXF's OpenApiFeature transitively depends on jakarta.xml.ws.WebServiceFeature. In many projects, jakarta.xml.ws-api is declared with <scope>provided</scope>, assuming the application server or buildpack supplies it. However, sap_java_buildpack_jakarta does not provide jakarta.xml.ws-api. This causes a runtime crash:
NoClassDefFoundError: jakarta/xml/ws/WebServiceFeature
Detect:
find . -name "pom.xml" -not -path "*/target/*" -exec grep -B2 -A2 "jakarta.xml.ws-api" {} \;
If the dependency exists with <scope>provided</scope>, remove the scope (or change to compile) so it gets packaged into the WAR:
Before:
<dependency>
<groupId>jakarta.xml.ws</groupId>
<artifactId>jakarta.xml.ws-api</artifactId>
<version>4.0.2</version>
<scope>provided</scope>
</dependency>
After:
<dependency>
<groupId>jakarta.xml.ws</groupId>
<artifactId>jakarta.xml.ws-api</artifactId>
<version>4.0.2</version>
</dependency>
Note: This applies specifically when deploying to sap_java_buildpack_jakarta. Other application servers (e.g., full Jakarta EE servers like TomEE) may provide this API, but the SAP Java Buildpack with Tomcat does not.
6e: Update web.xml Servlet Configuration (if applicable)
If the application configures OpenAPI/Swagger via web.xml servlet:
Before:
<servlet>
<servlet-name>Swagger</servlet-name>
<servlet-class>io.swagger.jaxrs.listing.ApiListingResource</servlet-class>
</servlet>
After:
<servlet>
<servlet-name>OpenApi</servlet-name>
<servlet-class>io.swagger.v3.jaxrs2.integration.OpenApiServlet</servlet-class>
<init-param>
<param-name>openApi.configuration.resourcePackages</param-name>
<param-value>your.api.package</param-value>
</init-param>
</servlet>
<servlet-mapping>
<servlet-name>OpenApi</servlet-name>
<url-pattern>/openapi/*</url-pattern>
</servlet-mapping>
Step 7: Migrate commons-fileupload to fileupload2 (Conditional)
The original commons-fileupload library depends on javax.servlet and cannot compile against Jakarta Servlet 6.0. The replacement is commons-fileupload2-jakarta-servlet6, which has breaking API changes.
This step is conditional. Only apply if the detection check below returns results.
Detect
find . -name "pom.xml" -not -path "*/target/*" -exec grep -l "<artifactId>commons-fileupload</artifactId>" {} \;
grep -r "org.apache.commons.fileupload\." --include="*.java" . | grep -v "fileupload2"
If any of the above commands return results, apply the transformations below.
7a: Replace Maven Dependency
Before:
<dependency>
<groupId>commons-fileupload</groupId>
<artifactId>commons-fileupload</artifactId>
<version>1.4</version>
</dependency>
After:
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-fileupload2-jakarta-servlet6</artifactId>
<version>2.0.0-M2</version>
</dependency>
Note: Check Maven Central for the latest version.
7b: Update Import Statements
| Before | After |
|---|
org.apache.commons.fileupload.FileItem | org.apache.commons.fileupload2.core.FileItem |
org.apache.commons.fileupload.FileItemFactory | org.apache.commons.fileupload2.core.DiskFileItemFactory |
org.apache.commons.fileupload.FileUploadException | org.apache.commons.fileupload2.core.FileUploadException |
org.apache.commons.fileupload.disk.DiskFileItemFactory | org.apache.commons.fileupload2.core.DiskFileItemFactory |
org.apache.commons.fileupload.servlet.ServletFileUpload | org.apache.commons.fileupload2.jakarta.servlet6.JakartaServletFileUpload |
7c: Update Factory Construction
The DiskFileItemFactory constructor changed to a builder pattern:
Before:
FileItemFactory factory = new DiskFileItemFactory();
ServletFileUpload upload = new ServletFileUpload(factory);
After:
DiskFileItemFactory factory = DiskFileItemFactory.builder().get();
JakartaServletFileUpload upload = new JakartaServletFileUpload(factory);
7d: Handle IOException on FileItem Methods
FileItem.get() and FileItem.delete() now throw IOException in fileupload2. Any code calling these methods must add exception handling:
Before:
byte[] data = fileItem.get();
fileItem.delete();
After:
try {
byte[] data = fileItem.get();
} catch (IOException e) {
throw new RuntimeException("Failed to read uploaded file", e);
}
try {
fileItem.delete();
} catch (IOException e) {
}
Alternatively, if the calling method can propagate IOException, add it to the throws clause.
Step 8: Update Dependency Versions
Ensure dependencies use Jakarta EE 10 compatible versions:
<dependencies>
<dependency>
<groupId>jakarta.servlet</groupId>
<artifactId>jakarta.servlet-api</artifactId>
<version>6.0.0</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>jakarta.persistence</groupId>
<artifactId>jakarta.persistence-api</artifactId>
<version>3.1.0</version>
</dependency>
<dependency>
<groupId>jakarta.annotation</groupId>
<artifactId>jakarta.annotation-api</artifactId>
<version>2.1.1</version>
<scope>provided</scope>
</dependency>
</dependencies>
Step 9: Fix Test Code — Jakarta Servlet 6.0 Interface Evolution
After OpenRewrite runs, test code with hand-written servlet mocks or stubs will fail to compile for two reasons:
- New abstract methods added in Jakarta Servlet 6.0 — your mock class is missing implementations
- Removed deprecated methods — your mock class has
@Override on methods that no longer exist in the interface
Note: OpenRewrite does not automatically fix either of these issues in hand-written mock classes.
Detect Affected Files
grep -rl "implements.*HttpServletRequest\|implements.*HttpServletResponse\|implements.*ServletContext\|implements.*ServletRequest\|implements.*HttpSession" --include="*.java" .
If this returns results, those files need fixing.
Part A: Remove @Override for Removed/Deprecated Methods
Jakarta Servlet 6.0 removed several deprecated methods. If your mock overrides these, the @Override annotation will cause a compile error because the method no longer exists in the interface. Remove the @Override annotation (you can keep the method body if your tests call it directly, or delete the method entirely).
Methods removed from HttpServletRequest (inherited from ServletRequest):
public String getRealPath(String path) {
return null;
}
Methods removed from HttpServletRequest:
public boolean isRequestedSessionIdFromUrl() {
return false;
}
Methods removed from ServletContext:
public String getRealPath(String path) {
return null;
}
Tip: Compile first (mvn test-compile) and look for errors like method does not override or implement a method from a supertype. Each such error means @Override must be removed.
Part B: Add Stub Methods for New Abstract Methods
For each affected class, add the missing method implementations that throw UnsupportedOperationException (or return a sensible default). These are the new abstract methods added in Jakarta Servlet 6.0:
HttpServletRequest (new in Servlet 6.0 — also satisfies ServletRequest):
@Override
public String getRequestId() {
throw new UnsupportedOperationException();
}
@Override
public String getProtocolRequestId() {
throw new UnsupportedOperationException();
}
@Override
public jakarta.servlet.ServletConnection getServletConnection() {
throw new UnsupportedOperationException();
}
ServletContext (new in Servlet 6.0):
@Override
public String getRequestCharacterEncoding() {
return null;
}
@Override
public void setRequestCharacterEncoding(String encoding) {
}
@Override
public String getResponseCharacterEncoding() {
return null;
}
@Override
public void setResponseCharacterEncoding(String encoding) {
}
@Override
public int getSessionTimeout() {
return 0;
}
@Override
public void setSessionTimeout(int sessionTimeout) {
}
@Override
public jakarta.servlet.ServletRegistration.Dynamic addJspFile(String servletName, String jspFile) {
return null;
}
HttpServletResponse (new in Servlet 6.0):
@Override
public void sendRedirect(String location, int sc, boolean clearBuffer) throws IOException {
throw new UnsupportedOperationException();
}
HttpSession (new in Servlet 6.0):
@Override
public Accessor getAccessor() {
throw new UnsupportedOperationException();
}
Note: The exact set of missing methods depends on which interfaces your test code implements and which methods were already overridden. The compiler error messages will tell you exactly which methods are missing.
Option C: Replace with Mockito 5.x (Recommended for Maintainability)
If the project uses Mockito (or can add it), replace hand-written mock classes entirely. This is future-proof against further interface evolution.
CRITICAL: If the project already uses Mockito 1.x (mockito-all), you must upgrade to Mockito 5.x. Mockito 1.x uses cglib for bytecode generation, which is blocked by Java 25's module system (InaccessibleObjectException / IllegalAccessError at test runtime). Mockito 5.x uses ByteBuddy, which supports Java 25+.
Detect Mockito Version Issue
find . -name "pom.xml" -not -path "*/target/*" -exec grep -l "mockito-all" {} \;
find . -name "pom.xml" -not -path "*/target/*" -exec grep -A1 "mockito-core" {} \; | grep -E "<version>[1-4]"
If either returns results, upgrade:
Before:
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-all</artifactId>
<version>1.10.19</version>
<scope>test</scope>
</dependency>
After:
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-core</artifactId>
<version>5.14.2</version>
<scope>test</scope>
</dependency>
Note: mockito-all was a fat JAR that bundled cglib and other dependencies. It was discontinued. mockito-core is the correct artifact for Mockito 5.x. The core API (spy, doReturn, when, mock) is unchanged, but several supporting APIs have breaking changes — see below.
Replace hand-written mocks (optional but recommended):
Before (hand-written mock):
public class MockHttpServletRequest implements HttpServletRequest {
private Map<String, String> parameters = new HashMap<>();
}
After (Mockito):
import static org.mockito.Mockito.*;
HttpServletRequest request = mock(HttpServletRequest.class);
when(request.getParameter("name")).thenReturn("value");
when(request.getMethod()).thenReturn("GET");
Mockito 5.x API Breaking Changes
Mockito 5.x removed and relocated several deprecated APIs. These cause compile errors after upgrading from Mockito 1.x/2.x/3.x:
org.mockito.Matchers → org.mockito.ArgumentMatchers
The Matchers class was removed in Mockito 5.x. All static imports must be updated:
Detect:
grep -r "org.mockito.Matchers" --include="*.java" src/test/
Fix (bulk replacement):
grep -rl "org.mockito.Matchers" --include="*.java" src/test/ | xargs sed -i 's/org\.mockito\.Matchers/org.mockito.ArgumentMatchers/g'
org.mockito.runners.MockitoJUnitRunner → org.mockito.junit.MockitoJUnitRunner
The runners sub-package was removed:
Detect:
grep -r "org.mockito.runners.MockitoJUnitRunner" --include="*.java" src/test/
Fix (bulk replacement):
grep -rl "org.mockito.runners.MockitoJUnitRunner" --include="*.java" src/test/ | xargs sed -i 's/org\.mockito\.runners\.MockitoJUnitRunner/org.mockito.junit.MockitoJUnitRunner/g'
Strict Stubbing — UnnecessaryStubbingException
Mockito 5.x defaults to strict stubbing when using @RunWith(MockitoJUnitRunner.class). Tests that set up when(...) stubs that are never called will fail with UnnecessaryStubbingException at runtime.
Detect: Run mvn test and look for errors containing unnecessary Mockito stubbings.
Fix: Switch affected test classes to lenient mode:
Before:
@RunWith(MockitoJUnitRunner.class)
public class MyServiceTest {
After:
@RunWith(MockitoJUnitRunner.Silent.class)
public class MyServiceTest {
Note: The ideal fix is to remove the unnecessary stubs, but Silent.class is a safe quick fix that preserves test behavior. Only use it for tests that actually have unused stubs — do not apply it to all test classes preemptively.
EqualsVerifier 3.x Breaking Changes (Conditional)
If the project uses nl.jqno.equalsverifier version 3.x+, several API changes may break tests:
Detect:
grep -A1 "equalsverifier" pom.xml | grep "<version>"
grep -r "allFieldsShouldBeUsed" --include="*.java" src/test/
allFieldsShouldBeUsed() removed
In EqualsVerifier 3.x, allFieldsShouldBeUsed() was removed because it is now the default behavior. Remove the call:
Before:
EqualsVerifier.forClass(MyClass.class)
.allFieldsShouldBeUsed()
.usingGetClass()
.suppress(Warning.NONFINAL_FIELDS)
.verify();
After:
EqualsVerifier.forClass(MyClass.class)
.usingGetClass()
.suppress(Warning.NONFINAL_FIELDS)
.verify();
New stricter field validation
EqualsVerifier 3.x may flag fields not used in equals() or classes that inherit equals() directly from Object. Suppress with the appropriate Warning:
.suppress(Warning.ALL_FIELDS_SHOULD_BE_USED)
.suppress(Warning.INHERITED_DIRECTLY_FROM_OBJECT)
Verify Test Compilation
mvn test-compile
This must succeed before proceeding. If it fails, read the compiler errors — they will list the exact missing methods for each class.
Step 10: Add JAXB Dependencies for Java 25 (Conditional)
Java 25 removed the javax.xml.bind module (JAXB) from the JDK. Libraries that depend on JAXB — such as Apache POI (OOXML/PPTX/XLSX processing), JAXB-based XML marshalling, or older SOAP clients — will fail at runtime with ClassNotFoundException: javax.xml.bind.JAXBException.
This step is conditional. Only apply if the detection check below returns results.
Detect
find . -name "pom.xml" -not -path "*/target/*" -exec grep -l -E "apache.*poi|jaxb|jaxws|javax.xml.bind" {} \;
grep -r "javax.xml.bind" --include="*.java" .
find . -name "pom.xml" -not -path "*/target/*" -exec grep -l "poi-ooxml" {} \;
If any commands return results, the application likely needs explicit JAXB dependencies.
Add JAXB API and Runtime
Add both the API and implementation — the API alone is not sufficient for runtime operation:
<dependency>
<groupId>javax.xml.bind</groupId>
<artifactId>jaxb-api</artifactId>
<version>2.3.1</version>
</dependency>
<dependency>
<groupId>com.sun.xml.bind</groupId>
<artifactId>jaxb-impl</artifactId>
<version>2.3.9</version>
</dependency>
Scope guidance:
- If JAXB is used in main code (XML marshalling, SOAP clients): use default scope (compile)
- If JAXB is only needed by test dependencies (e.g., POI in test code): use
<scope>test</scope>
- If JAXB is needed by a runtime dependency that doesn't expose JAXB types in its API: use
<scope>runtime</scope>
Why Both API and Implementation?
The jaxb-api artifact provides the javax.xml.bind.* interfaces and annotations. The jaxb-impl artifact provides the actual marshalling/unmarshalling engine. Without both, libraries like Apache POI will either throw ClassNotFoundException (missing API) or silently fail to parse XML content (missing implementation, e.g., PPTX slides returning 0 pages).
Note: If the project has already migrated to Jakarta XML Binding (jakarta.xml.bind.*), this step is not needed. This is specifically for third-party libraries that still use the old javax.xml.bind package (like Apache POI 3.x/4.x).
No new configuration files required for this skill.
CF Services
No CF services required for this skill.
Verification
1. Compile Check (Main Code)
mvn clean compile
Should complete without errors.
2. Compile Check (Test Code)
mvn test-compile
Should complete without errors. If it fails, see Step 9 above for fixing Jakarta Servlet 6.0 interface evolution issues in test mocks.
3. Verify No javax.* Imports Remain
grep -rh "^import javax\." src/main/java/ | grep -v "javax.sql" | grep -v "javax.naming"
4. Run Unit Tests
mvn test
Common Issues
Issue: OpenRewrite recipe not found
Solution: Ensure you have internet access and Maven can download plugins.
Issue: Compilation errors after migration
Cause: Some APIs have changed between Java EE and Jakarta EE.
Solution: Check specific API documentation for breaking changes.
Issue: Test code fails to compile after OpenRewrite migration
Cause: Jakarta Servlet 6.0 added new abstract methods to interfaces like HttpServletRequest, ServletContext, and HttpServletResponse. It also removed deprecated methods like getRealPath() and isRequestedSessionIdFromUrl(). Hand-written mock/stub classes in test code that implement these interfaces will have both missing new methods and invalid @Override annotations on removed methods.
Solution: See Step 9 above. Remove @Override on removed methods, add stubs for new methods, or replace hand-written mocks with Mockito.
Issue: CXF Swagger module not found after Jakarta migration
Cause: CXF 4.x (required for jakarta.ws.rs.*) removed cxf-rt-rs-service-description-swagger. The Swagger 2 module no longer exists in CXF 4.x.
Solution: See Step 6 above. Replace with cxf-rt-rs-service-description-openapi-v3 and migrate annotations from io.swagger.annotations to io.swagger.v3.oas.annotations.
Issue: commons-fileupload compilation errors after Jakarta migration
Cause: The original commons-fileupload depends on javax.servlet and cannot compile against Jakarta Servlet 6.0. The replacement library commons-fileupload2-jakarta-servlet6 has breaking API changes: different package names, builder-pattern factory construction, and FileItem.get()/delete() now throw IOException.
Solution: See Step 7 above. Replace the dependency and update all import statements, factory construction, and exception handling.
Issue: javax.sql imports flagged
Note: javax.sql.* is part of the JDK, not Java EE. These imports should NOT be changed.
Issue: ClassNotFoundException: com.fasterxml.jackson.jaxrs.json.JacksonJaxbJsonProvider at runtime
Cause: The Jackson JAX-RS provider package changed from com.fasterxml.jackson.jaxrs (javax) to com.fasterxml.jackson.jakarta.rs (jakarta). OpenRewrite updates Java import statements but does not update class references in web.xml <init-param> values. The CXFServlet fails to initialize because it cannot find the old class name.
Solution: See Step 5 above. Update the jaxrs.providers init-param in web.xml and the Maven dependency from jackson-jaxrs-json-provider to jackson-jakarta-rs-json-provider. Important: The class was also renamed — JacksonJaxbJsonProvider does not exist in the Jakarta artifact. Use com.fasterxml.jackson.jakarta.rs.json.JacksonJsonProvider instead. If you first update only the package (to com.fasterxml.jackson.jakarta.rs.json.JacksonJaxbJsonProvider), you will get a second ClassNotFoundException.
Issue: WELD-001474 — CDI beans ignored because jakarta.persistence.RollbackException not found
Cause: jakarta.persistence-api is set to <scope>provided</scope> in pom.xml. Tomcat is a servlet container, not a full Jakarta EE server — it does not provide JPA classes. With provided scope, the JPA API JAR is excluded from the WAR, so Weld cannot load any class that references jakarta.persistence.* types (e.g., RollbackException, NoResultException). All DAO classes are silently skipped, causing cascading WELD-001408: Unsatisfied dependencies errors for every injected DAO.
Solution: Remove <scope>provided</scope> from the jakarta.persistence-api dependency. The servlet API (jakarta.servlet-api) should remain provided (Tomcat provides it), but JPA, CDI, and other Jakarta EE APIs that Tomcat does not provide must be packaged in the WAR.
Issue: Mockito tests fail with InaccessibleObjectException or IllegalAccessError on Java 25
Cause: Mockito 1.x (mockito-all) uses cglib for bytecode generation. Java 25's module system blocks cglib's reflective access, causing all mocked tests to fail at runtime.
Solution: See Step 9 Option C above. Replace mockito-all with mockito-core:5.14.2 which uses ByteBuddy (Java 25 compatible). The test API (spy, when, doReturn, mock) is unchanged.
Issue: ClassNotFoundException: javax.xml.bind.JAXBException on Java 25
Cause: Java 25 removed the javax.xml.bind module from the JDK. Libraries like Apache POI that use JAXB internally will fail at runtime.
Solution: See Step 10 above. Add jaxb-api:2.3.1 and jaxb-impl:2.3.9 as dependencies. Both are required — API alone causes silent parsing failures.
Issue: NoClassDefFoundError: jakarta/xml/ws/WebServiceFeature at runtime
Cause: CXF's OpenApiFeature transitively depends on jakarta.xml.ws.WebServiceFeature. If jakarta.xml.ws-api is at provided scope, it won't be packaged into the WAR. The sap_java_buildpack_jakarta (Tomcat) does not provide this API, unlike full Jakarta EE servers.
Solution: See Step 6d above. Remove <scope>provided</scope> from jakarta.xml.ws-api so it ships in the WAR.
Issue: Test compilation fails with "cannot find symbol: method anyString()" or "method eq()"
Cause: Mockito 5.x removed the org.mockito.Matchers class. Static imports like import static org.mockito.Matchers.any no longer resolve.
Solution: See Step 9 — Mockito 5.x API Breaking Changes above. Replace org.mockito.Matchers with org.mockito.ArgumentMatchers in all test files.
Issue: Test compilation fails with "cannot find symbol: MockitoJUnitRunner"
Cause: Mockito 5.x moved MockitoJUnitRunner from org.mockito.runners to org.mockito.junit.
Solution: See Step 9 — Mockito 5.x API Breaking Changes above. Replace org.mockito.runners.MockitoJUnitRunner with org.mockito.junit.MockitoJUnitRunner.
Issue: Tests fail at runtime with UnnecessaryStubbingException
Cause: Mockito 5.x defaults to strict stubbing. Tests with when(...) stubs that are never invoked during the test are rejected.
Solution: See Step 9 — Strict Stubbing above. Switch the @RunWith to MockitoJUnitRunner.Silent.class for affected test classes, or remove the unused stubs.
Issue: EqualsVerifier tests fail with "allFieldsShouldBeUsed()" not found
Cause: EqualsVerifier 3.x removed the allFieldsShouldBeUsed() method because it is now the default.
Solution: See Step 9 — EqualsVerifier 3.x Breaking Changes above. Remove the allFieldsShouldBeUsed() call.
Issue: EqualsVerifier fails with "Significant fields: equals does not use X" or "Equals is inherited directly from Object"
Cause: EqualsVerifier 3.x is stricter about field usage and equals() inheritance.
Solution: Suppress with Warning.ALL_FIELDS_SHOULD_BE_USED or Warning.INHERITED_DIRECTLY_FROM_OBJECT as appropriate.
Next Steps
After completing this skill, proceed to: