Skip to main content

jakarta-java25-migration

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.

Source facts

Repository
SAP-samples/btp-neo-java-app-migration
Last source activity
July 29, 2026 at 13:48
Detected SKILL.md language
English
Stars
6
Forks
0

Install options

The review-first prompt is selected by default. You can switch to a direct command or download a local copy.

Review the source files

Read SKILL.md and any companion files shown by SkillsMP before deciding whether to install.

Showing SKILL.md

SKILL.md
Source instructions · Read-only preview
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 ```xml <!-- Java 8 or 11 source/target --> <maven.compiler.source>1.8</maven.compiler.source> <maven.compiler.target>1.8</maven.compiler.target> <!-- OR --> <properties> <java.version>11</java.version> </properties> ``` ### In Java source files ```java // javax.* imports indicate pre-Jakarta EE 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. ```bash 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: ```bash mkdir -p .migration ``` Save the paths to `.migration/cf-migration-config.json` (create or update the file): ```json { "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:** ```xml <properties> <maven.compiler.source>1.8</maven.compiler.source> <maven.compiler.target>1.8</maven.compiler.target> </properties> ``` **After:** ```xml <properties> <maven.compiler.source>25</maven.compiler.source> <maven.compiler.target>25</maven.compiler.target> </properties> ``` > 🛑 **HARD RULE — pin the runtime JRE in the mtad, or the app 500s on every request.** > You just set `maven.compiler.target` to 25, so the WAR is Java 25 bytecode (class > file version 69). `sap_java_buildpack_jakarta`'s **default** JRE is NOT 25 (it is > currently 21) — if you do not pin it, Tomcat cannot load your servlets and fails with > `java.lang.UnsupportedClassVersionError: ... class file version 69.0, this version of > the Java Runtime only recognizes class file versions up to 65.0`, marks the servlet > unavailable, and **every request returns HTTP 500** (the app still shows `running 1/1`, > so this is easy to miss until an integration test hits it). The deployed module's > `properties:` **MUST** include BOTH env vars: > > ```yaml > properties: > JBP_CONFIG_COMPONENTS: "jres: ['com.sap.xs.java.buildpack.jre.SAPMachineJRE']" > JBP_CONFIG_SAP_MACHINE_JRE: '{ version: 25.+ }' > ``` > > The `mta-descriptor` skill's `mtad-base.yaml` template already sets these — if you > start from it, keep them; if you hand-write the mtad, add them. > > ✅ **After writing the mtad, verify:** `grep JBP_CONFIG_SAP_MACHINE_JRE <mtad>` must > return the line above. Rule of thumb: **`maven.compiler.target` must equal the major > version in `JBP_CONFIG_SAP_MACHINE_JRE`** (both 25). ### Step 2: Run OpenRewrite Migration Execute the OpenRewrite recipes to automatically migrate code: ```bash 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,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 > - **`-Drewrite.activeRecipes` must be passed exactly once.** Maven turns each `-D` into a Java system property, and a property has only one value per key — passing two `-D rewrite.activeRecipes=...` flags silently keeps only the last one and the first recipe is dropped without a warning. Always pass a single comma-separated list. See [the OpenRewrite Maven plugin reference](https://docs.openrewrite.org/reference/rewrite-maven-plugin) for the canonical syntax. 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 2b: Fix OpenRewrite side-effects in pom.xml OpenRewrite makes two changes that must be corrected after it runs: #### 2b-1: Restore `jakarta.servlet-api` to `provided` scope OpenRewrite replaces `javax.servlet:javax.servlet-api` with `jakarta.servlet:jakarta.servlet-api` but sets `<scope>compile</scope>`. This is wrong — `sap_java_buildpack_jakarta` provides the servlet API at runtime, so the scope must be `provided`. If left as `compile`, the servlet API JAR is packaged into the WAR and conflicts with the buildpack's version, causing all servlets to return 404 after a successful deploy. **Detect and fix:** ```bash # Check if scope was set to compile grep -A4 "jakarta.servlet-api" pom.xml | grep "compile" ``` If the above returns a result, change the scope: ```xml <!-- WRONG — causes 404 after deploy --> <dependency> <groupId>jakarta.servlet</groupId> <artifactId>jakarta.servlet-api</artifactId> <version>6.0.0</version> <scope>compile</scope> </dependency> <!-- Change <scope>compile</scope> to <scope>provided</scope> — see CORRECT block below --> <!-- CORRECT --> <dependency> <groupId>jakarta.servlet</groupId> <artifactId>jakarta.servlet-api</artifactId> <version>6.0.0</version> <scope>provided</scope> </dependency> ``` > If `cf-tomcat-bom` is imported in `<dependencyManagement>`, the version tag can be omitted — the BOM manages it. #### 2b-2: Replace `javax.servlet:jstl` with Jakarta JSTL (Conditional) OpenRewrite does NOT migrate the old JSTL library. If `javax.servlet:jstl` is present in pom.xml, it must be replaced with the Jakarta EE 10 compatible version. **Detect:** ```bash grep -B2 "jstl" pom.xml | grep "javax.servlet" ``` If the above returns a result, replace the dependency: **Before:** ```xml <dependency> <groupId>javax.servlet</groupId> <artifactId>jstl</artifactId> <version>1.2</version> </dependency> ``` **After:** ```xml <dependency> <groupId>jakarta.servlet.jsp.jstl</groupId> <artifactId>jakarta.servlet.jsp.jstl-api</artifactId> <version>3.0.0</version> </dependency> <dependency> <groupId>org.glassfish.web</groupId> <artifactId>jakarta.servlet.jsp.jstl</artifactId> <version>3.0.1</version> </dependency> ``` ### 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:** ```bash grep -r "opencmis" pom.xml grep -r "org.apache.chemistry" src/main/java/ ``` **If OpenCMIS is used, add these specific exclusions:** ```xml <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:** ```xml <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:** ```xml <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:** ```bash grep -r "com.fasterxml.jackson.jaxrs" --include="*.xml" src/main/webapp/ ``` If this returns results, update the class name: **Before:** ```xml <init-param> <param-name>jaxrs.providers</param-name> <param-value> com.fasterxml.jackson.jaxrs.json.JacksonJaxbJsonProvider </param-value> </init-param> ``` **After:** ```xml <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:** ```xml <dependency> <groupId>com.fasterxml.jackson.jaxrs</groupId> <artifactId>jackson-jaxrs-json-provider</artifactId> </dependency> ``` **After:** ```xml
View on GitHub
This SKILL.md is very large, so SkillsMP previews the first section here. View on GitHub