Resolves third-party library compatibility issues during Neo to Cloud Foundry migration, detecting breaks in Java 25, Jakarta EE 10, HANA Cloud for libraries like Liquibase, Flyway, Guice, CDI, POI, and others.
npx claudepluginhub sap-samples/btp-neo-java-app-migration --plugin sap-btp-neo-migrationThis skill is limited to using the following tools:
Resolve third-party library compatibility issues that arise during the Neo to Cloud Foundry migration.
Guides Next.js Cache Components and Partial Prerendering (PPR): 'use cache' directives, cacheLife(), cacheTag(), revalidateTag() for caching, invalidation, static/dynamic optimization. Auto-activates on cacheComponents: true.
Processes PDFs: extracts text/tables/images, merges/splits/rotates pages, adds watermarks, creates/fills forms, encrypts/decrypts, OCRs scans. Activates on PDF mentions or output requests.
Share bugs, ideas, or general feedback.
Resolve third-party library compatibility issues that arise during the Neo to Cloud Foundry migration.
When migrating from Neo (Java 8, Java EE) to CF (Java 25, Jakarta EE 10, HANA Cloud), many third-party libraries break due to:
javax.xml.bind, javax.xml.ws, and other javax.* modules from the JDKjavax.servlet, javax.ws.rs, etc. won't link against Jakarta EE 10This skill scans the project's dependencies, identifies known compatibility issues, and applies targeted fixes.
Note: The
jakarta-java25-migrationskill handles the core javax→jakarta migration and Java 25 language upgrade. This skill handles third-party library-specific issues that the core migration doesn't cover.
This skill applies if any of these patterns are found:
<!-- Schema migration tools -->
<artifactId>liquibase-core</artifactId>
<artifactId>flyway-core</artifactId>
<!-- DI frameworks -->
<artifactId>guice</artifactId>
<artifactId>guice-servlet</artifactId>
<artifactId>weld-servlet-core</artifactId>
<!-- Known problematic libraries -->
<artifactId>poi-ooxml</artifactId>
<artifactId>itextpdf</artifactId>
<artifactId>ehcache</artifactId>
<artifactId>hazelcast</artifactId>
<artifactId>quartz</artifactId>
<artifactId>retrofit</artifactId>
// DI framework imports
import com.google.inject.*;
import com.google.inject.servlet.GuiceServletContextListener;
// Liquibase programmatic usage
import liquibase.Liquibase;
import liquibase.integration.servlet.LiquibaseServletListener;
// CDI annotations
import javax.enterprise.context.*;
import jakarta.enterprise.context.*;
<!-- Liquibase listener -->
<listener-class>liquibase.integration.servlet.LiquibaseServletListener</listener-class>
<!-- Guice filter/listener -->
<listener-class>com.google.inject.servlet.GuiceServletContextListener</listener-class>
<filter-class>com.google.inject.servlet.GuiceFilter</filter-class>
Before invoking this skill, ensure you have invoked:
jakarta-java25-migration - Use the jakarta-java25-migration skill
sdk-replacement - Use the sdk-replacement skill
This step is conditional. Only apply if schema migration tools are detected.
# Liquibase
find . -name "pom.xml" -not -path "*/target/*" -exec grep -l "liquibase" {} \;
# Flyway
find . -name "pom.xml" -not -path "*/target/*" -exec grep -l "flyway" {} \;
If either returns output, apply the relevant fix below.
Liquibase 3.x has two critical issues on CF:
JAXB dependency — Liquibase 3.x internally uses javax.xml.bind for parsing XML changelogs. Java 25 removed javax.xml.bind from the JDK, causing:
NoClassDefFoundError: javax/xml/bind/annotation/XmlElement
Unknown database: HDB — Liquibase 3.x doesn't recognize HANA Cloud's database identifier. It falls back to UnsupportedDatabase, which can fail on schema-specific operations.
Fix: Upgrade Liquibase to 4.x and add the HANA extension.
Note: Check Maven Central for the latest 4.x version at the time of migration.
Before:
<dependency>
<groupId>org.liquibase</groupId>
<artifactId>liquibase-core</artifactId>
<version>3.8.2</version> <!-- or any 3.x -->
</dependency>
<!-- Old HANA community extension (may be present or commented out) -->
<dependency>
<groupId>com.github.lbitonti</groupId>
<artifactId>liquibase-hana</artifactId>
<version>3.0</version>
</dependency>
After:
<dependency>
<groupId>org.liquibase</groupId>
<artifactId>liquibase-core</artifactId>
<version>4.30.0</version>
</dependency>
<!-- Official HANA extension for Liquibase 4.x -->
<dependency>
<groupId>org.liquibase.ext</groupId>
<artifactId>liquibase-hanadb</artifactId>
<version>4.30.0</version>
</dependency>
<!-- SLF4J bridge for Liquibase logging -->
<dependency>
<groupId>com.mattbertolini</groupId>
<artifactId>liquibase-slf4j</artifactId>
<version>5.1.0</version>
</dependency>
Important: Remove the old
com.github.lbitonti:liquibase-hanadependency if present (even if commented out). It is incompatible with Liquibase 4.x. See the Liquibase HANA Extension for details.
Flyway 9.x+ supports Jakarta EE and HANA Cloud natively.
Note: Flyway 10.x requires Java 17+. Check Flyway docs for the latest version.
# Check current Flyway version
find . -name "pom.xml" -not -path "*/target/*" -exec grep -A1 "flyway" {} \;
If Flyway version is < 9.0, upgrade:
<dependency>
<groupId>org.flywaydb</groupId>
<artifactId>flyway-core</artifactId>
<version>10.20.1</version>
</dependency>
<!-- For HANA support -->
<dependency>
<groupId>org.flywaydb</groupId>
<artifactId>flyway-database-saphana</artifactId>
<version>10.20.1</version>
</dependency>
This step is conditional. Only apply if a DI framework is detected.
# Google Guice
grep -r "com.google.inject\|GuiceServletContextListener\|GuiceFilter" --include="*.java" .
find . -name "pom.xml" -not -path "*/target/*" -exec grep -l "guice" {} \;
# CDI (Weld)
grep -r "javax.enterprise.context\|jakarta.enterprise.context\|@RequestScoped\|@ApplicationScoped" --include="*.java" .
find . -name "pom.xml" -not -path "*/target/*" -exec grep -l "weld" {} \;
# HK2
find . -name "pom.xml" -not -path "*/target/*" -exec grep -l "hk2" {} \;
If any commands return output, check for the common issues below.
If the application uses both a DI framework and a schema migration tool (Liquibase, Flyway), listener ordering in web.xml becomes critical.
Problem: Schema migration tools like Liquibase's LiquibaseServletListener perform their own JNDI lookup for the DataSource. This doesn't work when:
DataSource is provided by a DI framework that must initialize firstThe required initialization order in web.xml is:
1. DI Framework Listener → Initializes DI container, creates DataSource
2. Schema Migration Listener → Gets DataSource from DI, runs migrations
3. Application Listener → Application starts with migrated schema
Solution: Replace Liquibase's built-in LiquibaseServletListener (or Flyway's equivalent) with a custom ServletContextListener that retrieves the DataSource from your DI container.
web.xml listener ordering:
<!-- Order matters! DI must initialize before schema migration -->
<listener>
<!-- 1. DI framework initializes first -->
<listener-class>com.example.app.GuiceContextListener</listener-class>
</listener>
<listener>
<!-- 2. Schema migration runs after DI provides the DataSource -->
<listener-class>com.example.app.CFLiquibaseRunner</listener-class>
</listener>
<listener>
<!-- 3. Application initializes after schema is ready -->
<listener-class>com.example.app.AppInitializer</listener-class>
</listener>
Custom migration runner template (adapt to your DI framework):
import jakarta.servlet.ServletContextEvent;
import jakarta.servlet.ServletContextListener;
import javax.sql.DataSource;
import liquibase.Liquibase;
import liquibase.database.Database;
import liquibase.database.DatabaseFactory;
import liquibase.database.jvm.JdbcConnection;
import liquibase.resource.ClassLoaderResourceAccessor;
import java.sql.Connection;
/**
* Custom Liquibase runner that retrieves the DataSource from the DI container
* instead of performing a direct JNDI lookup.
*
* ADAPT: Replace getDataSourceFromDI() with your DI framework's lookup method.
*/
public class CFLiquibaseRunner implements ServletContextListener {
@Override
public void contextInitialized(ServletContextEvent sce) {
try {
// ADAPT: Get DataSource from your DI framework
// For Guice: Injector injector = (Injector) sce.getServletContext().getAttribute(Injector.class.getName());
// DataSource ds = injector.getInstance(DataSource.class);
// For CDI: DataSource ds = CDI.current().select(DataSource.class).get();
DataSource dataSource = getDataSourceFromDI(sce);
try (Connection conn = dataSource.getConnection()) {
Database database = DatabaseFactory.getInstance()
.findCorrectDatabaseImplementation(new JdbcConnection(conn));
try (Liquibase liquibase = new Liquibase(
"db/changelog/db.changelog-master.xml",
new ClassLoaderResourceAccessor(),
database)) {
liquibase.update("");
}
}
} catch (Exception e) {
throw new RuntimeException("Liquibase migration failed", e);
}
}
private DataSource getDataSourceFromDI(ServletContextEvent sce) {
// TODO: Implement based on your DI framework
throw new UnsupportedOperationException("Adapt this method to your DI framework");
}
@Override
public void contextDestroyed(ServletContextEvent sce) {
// no-op
}
}
Note: This is a template — you must adapt
getDataSourceFromDI()to your specific DI framework. The key principle is: let the DI container manage the DataSource, and have the migration runner retrieve it from the container rather than doing its own JNDI lookup.
When using Jersey with CDI (Weld) on Tomcat (not TomEE), the jersey-cdi2-se module bootstraps Weld in SE (standalone) mode. Weld SE scans for META-INF/beans.xml inside classpath entries to discover bean archives. However, a WAR's standard CDI descriptor at WEB-INF/beans.xml is not recognized by Weld SE — only by Weld Servlet.
Symptom: All @Inject points fail at deployment with:
WELD-001408: Unsatisfied dependencies for type UserDAO with qualifiers @Default
WELD-001408: Unsatisfied dependencies for type Notifier with qualifiers @Default
...
The errors appear for every injected bean even though WEB-INF/beans.xml exists with bean-discovery-mode="all".
Detection:
# Check if using jersey-cdi2-se with Weld on Tomcat
grep -l "jersey-cdi2-se" pom.xml
grep -l "weld-servlet" pom.xml
grep -l "cf-tomcat-bom" pom.xml
# Check if META-INF/beans.xml exists in resources
ls src/main/resources/META-INF/beans.xml 2>/dev/null
If all three dependencies are present AND src/main/resources/META-INF/beans.xml does NOT exist, apply this fix.
Fix: Add a beans.xml to src/main/resources/META-INF/ so it is packaged as WEB-INF/classes/META-INF/beans.xml in the WAR. This makes Weld SE treat the classes directory as a bean archive:
mkdir -p src/main/resources/META-INF
Create src/main/resources/META-INF/beans.xml:
<?xml version="1.0" encoding="UTF-8"?>
<beans 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/beans_4_0.xsd"
bean-discovery-mode="all"
version="4.0">
</beans>
Note: Keep the existing
WEB-INF/beans.xmlas well — it is still needed for Weld Servlet mode. The WAR should contain BOTHWEB-INF/beans.xmlandWEB-INF/classes/META-INF/beans.xml. This issue does NOT affect TomEE-based applications because TomEE provides its own CDI container (OpenWebBeans) and does not usejersey-cdi2-se.
After fixing the bean discovery issue (Step 2b), a second deployment error can occur: WELD-001409: Ambiguous dependencies for HttpServletRequest. With bean-discovery-mode="all", Weld SE discovers multiple providers for HttpServletRequest — typically two Jersey SupplierBeanBridge instances and one Weld built-in bean.
Symptom:
WELD-001409: Ambiguous dependencies for type HttpServletRequest with qualifiers @Default
at injection point [BackedAnnotatedField] @Inject private com.example.filter.MyFilter.request
Possible dependencies:
- org.glassfish.jersey.ext.cdi1x.internal.CdiComponentProvider$SupplierBeanBridge
- org.glassfish.jersey.ext.cdi1x.internal.CdiComponentProvider$SupplierBeanBridge
- Built-in Bean
Cause: JAX-RS filters and resource classes that use @Inject (CDI) for HttpServletRequest instead of @Context (JAX-RS). When both Jersey and Weld can provide HttpServletRequest, CDI cannot resolve the ambiguity.
Detection:
# Find Java files that @Inject HttpServletRequest
grep -rn "@Inject" --include="*.java" -A1 . | grep "HttpServletRequest"
Fix: Changing @Inject to @Context resolves this specific ambiguity error, but will lead to a runtime failure (WELD-000710) when the proxy is actually invoked. See Step 2e for the complete solution that removes servlet type field injection entirely.
Note: Only change
HttpServletRequestinjection. Other CDI beans (DAOs, services) must remain@Inject. This issue does NOT affect TomEE-based applications.
After fixing bean discovery (Step 2b) and HttpServletRequest ambiguity (Step 2c), a third deployment error can occur: WELD-001409: Ambiguous dependencies for Configuration. With bean-discovery-mode="all", Weld SE discovers the JAX-RS Application subclass (typically extending ResourceConfig) as a CDI managed bean. Since ResourceConfig implements jakarta.ws.rs.core.Configuration, it conflicts with Jersey's own InstanceBean that also provides Configuration.
Symptom:
WELD-001409: Ambiguous dependencies for type Configuration with qualifiers @Default
at injection point [BackedAnnotatedParameter] Parameter 2 of [BackedAnnotatedConstructor] @Inject public org.glassfish.jersey.internal.inject.ParamConverters$AggregatedProvider(@Context InjectionManager, @Context Configuration)
Possible dependencies:
- org.glassfish.jersey.inject.cdi.se.bean.InstanceBean
- Managed Bean [class com.example.rest.MyApplication] with qualifiers [@Any @Default]
Detection:
# Find JAX-RS Application subclass
grep -rn "extends ResourceConfig\|extends Application" --include="*.java" src/
# Check if it has @Vetoed
grep -B5 "extends ResourceConfig\|extends Application" --include="*.java" src/ | grep -c "Vetoed"
Fix: Add @jakarta.enterprise.inject.Vetoed to the Application subclass to exclude it from CDI bean discovery:
Before:
import org.glassfish.jersey.server.ResourceConfig;
public class MyApplication extends ResourceConfig {
After:
import jakarta.enterprise.inject.Vetoed;
import org.glassfish.jersey.server.ResourceConfig;
@Vetoed
public class MyApplication extends ResourceConfig {
Note: The
@Vetoedannotation tells Weld to completely ignore this class during bean discovery. Jersey manages its own Application instance — it should never be a CDI managed bean. This issue does NOT affect TomEE-based applications.
After fixing the HttpServletRequest ambiguity (Step 2c) and Configuration ambiguity (Step 2d), a runtime error occurs when any code invokes a method on an injected HttpServletRequest, HttpServletResponse, or HttpSession. The @Context annotation resolves the CDI ambiguity, but the underlying Weld SE proxy still cannot provide the actual servlet object.
Symptom:
WELD-000710: Cannot inject HttpServletRequest outside of a Servlet request
at com.example.filter.MyFilter.getCurrentUser(MyFilter.java:85)
This error occurs at request time (not deployment), when the proxy created by @Context HttpServletRequest tries to delegate to Weld SE's built-in HttpServletRequestBean, which requires an active servlet request context that Weld SE does not have.
Root cause: jersey-cdi2-se redirects all @Context injection through CDI. For servlet types (HttpServletRequest, HttpServletResponse, HttpSession), Weld's built-in beans take precedence over Jersey's CDI extension beans. These built-in beans only work in Weld Servlet mode, not in Weld SE mode. The same issue affects HttpServletResponse and HttpSession.
Detection:
# Find all field-level @Context HttpServletRequest/HttpServletResponse/HttpSession
grep -rn "@Context" --include="*.java" -A1 src/ | grep -E "HttpServletRequest|HttpServletResponse|HttpSession"
Fix: Remove all field-level @Context injection of servlet types. Replace with JAX-RS alternatives depending on the class type:
For ContainerRequestFilter classes — use ContainerRequestContext (passed to the filter() method):
Before:
public class MyFilter implements ContainerRequestFilter {
@Context
private HttpServletRequest request;
@Override
public void filter(ContainerRequestContext requestContext) {
String userId = request.getUserPrincipal().getName();
HttpSession session = request.getSession();
User user = (User) session.getAttribute("user");
}
}
After:
public class MyFilter implements ContainerRequestFilter {
@Override
public void filter(ContainerRequestContext requestContext) {
String userId = requestContext.getSecurityContext().getUserPrincipal().getName();
// Store resolved user as a request-scoped property for downstream resource methods
User user = resolveUser(userId);
requestContext.setProperty("user", user);
// Downstream resource methods can read it via:
// User user = (User) requestContext.getProperty("user");
}
For JAX-RS resource classes (@Path) — replace @Context HttpServletRequest with @Context SecurityContext (a JAX-RS type, not a servlet type — Weld SE does not intercept it):
Before:
@RequestScoped
@Path("api/v1")
public class MyService {
@Context
private HttpServletRequest httpRequest;
@GET @Path("/data")
public Response getData() {
String userId = httpRequest.getUserPrincipal().getName();
boolean isAdmin = httpRequest.isUserInRole("Admin");
}
}
After:
@RequestScoped
@Path("api/v1")
public class MyService {
@Context
private SecurityContext securityContext;
@GET @Path("/data")
public Response getData() {
String userId = securityContext.getUserPrincipal().getName();
boolean isAdmin = securityContext.isUserInRole("Admin");
}
}
For HttpServletResponse — move to method parameter injection (resolved by Jersey at invocation time, not by CDI):
Before:
@Context
private HttpServletResponse httpResponse;
@GET @Path("/report")
public HttpServletResponse getReport() {
httpResponse.setContentType("text/csv");
// ...
return httpResponse;
}
After:
@GET @Path("/report")
public HttpServletResponse getReport(@Context HttpServletResponse httpResponse) {
httpResponse.setContentType("text/csv");
// ...
return httpResponse;
}
For non-resource CDI beans (factories, utilities) — pass the needed values from callers instead of injecting HttpServletRequest:
Before:
@RequestScoped
public class MyFactory {
@Context
private HttpServletRequest httpRequest;
public void doWork() {
boolean isAdmin = httpRequest.isUserInRole("Admin");
}
}
After: @RequestScoped public class MyFactory {
public void doWork(boolean isAdmin) {
// use isAdmin directly — no field injection needed
}
}
**For `IdentityAdapter` or similar classes** that accept `HttpServletRequest` as a method parameter to extract user info — change to accept `java.security.Principal`:
**Before:**
```java
public String getUserName(HttpServletRequest request) {
Principal principal = request.getUserPrincipal();
// extract claims from principal...
}
After:
public String getUserName(Principal principal) {
// extract claims from principal...
}
Key insight:
jakarta.ws.rs.core.SecurityContextis a JAX-RS type, NOT a servlet type. Weld SE does not have a built-in bean for it, so Jersey's CDI extension provides it correctly. The affected servlet types are:HttpServletRequest,HttpServletResponse,HttpSession, andServletContext. This issue does NOT affect TomEE-based applications.
If the application uses Google Guice, check the version:
find . -name "pom.xml" -not -path "*/target/*" -exec grep -A1 "guice" {} \;
Guice 7.x+ supports jakarta.inject and jakarta.servlet. If on Guice < 7:
Note: Guice 7.x is a breaking change from Guice 4.x/5.x — it requires
jakarta.injectinstead ofjavax.inject. Since thejakarta-java25-migrationskill already migratesjavax.inject→jakarta.inject, Guice 7.x is the correct target. See the Guice 7.0 Release Notes for the full change list.
Before:
<dependency>
<groupId>com.google.inject</groupId>
<artifactId>guice</artifactId>
<version>4.2.3</version> <!-- or any < 7.x -->
</dependency>
<dependency>
<groupId>com.google.inject.extensions</groupId>
<artifactId>guice-servlet</artifactId>
<version>4.2.3</version>
</dependency>
After:
<dependency>
<groupId>com.google.inject</groupId>
<artifactId>guice</artifactId>
<version>7.0.0</version>
</dependency>
<dependency>
<groupId>com.google.inject.extensions</groupId>
<artifactId>guice-servlet</artifactId>
<version>7.0.0</version>
</dependency>
After fixing WELD-000710 (Step 2e), a runtime error can occur if any CDI bean uses @SessionScoped. Weld SE mode does not provide an active session context, so any @SessionScoped bean will fail when its proxy is first accessed.
Symptom:
WELD-001303: No active contexts for scope type jakarta.enterprise.context.SessionScoped
at com.example.adapter.IdentityAdapter$Proxy$_$$_WeldClientProxy.getUserName()
This error occurs at request time when the CDI proxy for a @SessionScoped bean tries to find an active session context.
Root cause: jersey-cdi2-se runs Weld in SE mode, which does not activate the session scope. On Neo with a full servlet container, @SessionScoped works because the servlet container manages HTTP sessions. On CF with Weld SE, there is no servlet session lifecycle.
Detection:
# Find all @SessionScoped beans
grep -rn "@SessionScoped" --include="*.java" src/
Fix: Change @SessionScoped to an appropriate alternative scope:
@ApplicationScoped — for stateless services, adapters, and utilities that don't hold per-user state. This is the most common replacement.@RequestScoped — for beans that need per-request isolation but not cross-request session state.Also remove Serializable interface and serialVersionUID if they were only there for session serialization.
Before:
@SessionScoped
public class IdentityAdapter implements Serializable {
private static final long serialVersionUID = 1L;
// ...
}
After:
@ApplicationScoped
public class IdentityAdapter {
// ...
}
Key insight: If the bean was using
@SessionScopedto cache user-specific data across requests (e.g., storing user info in a session), replace that caching with per-request property storage viaContainerRequestContext.setProperty()/getProperty()(see Step 2e) or pass the values explicitly from the caller. This issue does NOT affect TomEE-based applications.
For other third-party libraries found during detection, check this table:
| Library | Breaking Version | Minimum Compatible Version | Issue | Fix |
|---|---|---|---|---|
| Apache POI (OOXML) | 3.x, 4.x | 4.x (with JAXB deps) or 5.x | Uses javax.xml.bind internally for OOXML parsing | Add jaxb-api + jaxb-impl at appropriate scope (see jakarta-java25-migration Step 10) or upgrade to POI 5.x |
| iText | 5.x | 7.x+ (or OpenPDF 1.3+) | iText 5.x uses removed Java APIs | Upgrade or switch to OpenPDF |
| EhCache | 2.x | 3.10+ | javax.cache namespace | Upgrade to EhCache 3.x with jakarta.cache |
| Hazelcast | < 5.0 | 5.0+ | javax.cache namespace | Upgrade to 5.x |
| Quartz Scheduler | 2.3.x | 2.5.0+ | Java 25 reflection issues | Upgrade to 2.5.x+ |
| Retrofit | 1.x | 2.9+ | OkHttp 3.x incompatibility | Upgrade to Retrofit 2.x |
| Jackson | < 2.13 | 2.15+ | Java 25 module access warnings | Upgrade to 2.15+ |
| Hibernate | 5.x | 6.x+ | javax.persistence namespace | Upgrade to Hibernate 6.x (uses jakarta.persistence) |
| Log4j | 1.x | 2.x (Log4j2) | EOL, security vulnerabilities, Java 25 issues | Migrate to Log4j2 or SLF4J + Logback |
| ModelMapper | 2.x, 3.x | 3.x (with fix) | PropertyMap.configure() proxy NPE on enum fields with Java 25 | Replace PropertyMap with Converter for mappings that access enum fields (see Common Issues) |
| EclipseLink (MOXy) | 4.x | 4.x (with deps) | MOXy JSON provider needs jakarta.json-api not bundled transitively | Add jakarta.json-api + parsson (Eclipse JSON-P impl) to pom.xml (see Common Issues) |
Note: This table covers commonly encountered libraries. For libraries not listed, check their release notes for Java 25 and Jakarta EE 10 compatibility.
For any dependency not in the known table above:
# Check if any dependency still uses javax.* at runtime
# Build the WAR first
mvn clean package -DskipTests
# List all JARs in the WAR and check for javax.* usage
jar tf target/*.war | grep "WEB-INF/lib/" | sort
Heuristic: If a library's latest version was released before 2021, it likely has Java 25 or Jakarta EE issues. Check the library's documentation for migration guides.
For each flagged library:
javax.xml.bind, etc.) at the appropriate scopeNo new configuration files are created by this skill. Changes are limited to pom.xml dependency updates, web.xml listener ordering, and optional custom Java classes.
No CF services required for this skill.
mvn clean compile
mvn test-compile
mvn test
# Search for libraries that might still need JAXB at runtime
mvn dependency:tree | grep -i "jaxb\|xml.bind"
Cause: Another library (not Liquibase) still depends on javax.xml.bind.
Solution: Run mvn dependency:tree | grep -B5 "xml.bind" to find which dependency pulls it. Add jaxb-api + jaxb-impl at the appropriate scope, or upgrade the offending library.
Cause: Missing liquibase-hanadb extension.
Solution: Add org.liquibase.ext:liquibase-hanadb at the same version as liquibase-core.
Cause: Listener ordering in web.xml — Liquibase listener runs before DI framework listener.
Solution: Reorder listeners in web.xml: DI framework first, then schema migration, then application init. See Step 2a above.
Cause: jersey-cdi2-se bootstraps Weld SE which looks for META-INF/beans.xml in classpath entries, not WEB-INF/beans.xml.
Solution: Add src/main/resources/META-INF/beans.xml with bean-discovery-mode="all". See Step 2b above.
Cause: With bean-discovery-mode="all", both Jersey and Weld provide HttpServletRequest as CDI beans, creating ambiguity for @Inject injection points.
Solution: Change @Inject to @Context for HttpServletRequest in JAX-RS filters and resource classes. See Step 2c above.
Cause: With bean-discovery-mode="all", the JAX-RS Application subclass (extending ResourceConfig) is discovered as a CDI bean that implements Configuration, conflicting with Jersey's own InstanceBean.
Solution: Add @jakarta.enterprise.inject.Vetoed to the Application subclass. See Step 2d above.
Cause: jersey-cdi2-se runs Weld in SE mode. Weld SE's built-in beans for servlet types (HttpServletRequest, HttpServletResponse, HttpSession) require an active servlet request context that doesn't exist in SE mode. This occurs even with @Context (not just @Inject) because jersey-cdi2-se routes all injection through CDI.
Solution: Remove all field-level @Context/@Inject of servlet types. Use @Context SecurityContext (JAX-RS type) for principal/role access, ContainerRequestContext in filters, and method parameter @Context for HttpServletResponse. See Step 2e above.
Cause: jersey-cdi2-se runs Weld in SE mode, which does not activate the session scope. Any @SessionScoped bean proxy will throw this error when first accessed at request time.
Solution: Change @SessionScoped to @ApplicationScoped (for stateless beans) or @RequestScoped (for per-request beans). Remove Serializable interface if it was only there for session serialization. See Step 2g above.
Cause: ModelMapper's PropertyMap.configure() creates CGLIB/ByteBuddy proxies to record property access. When a getter returns an enum type (e.g., getType() returns BenefitType), the proxy returns null because enums cannot be proxied. Calling .toString() on the null enum causes an NPE. This manifests as NullPointerException: Cannot invoke "MyEnum.toString()" because the return value of "MyEntity.getType()" is null during PropertyMap.configure(), not during actual mapping.
Solution: Replace the PropertyMap with a Converter (using AbstractConverter) that maps objects manually at mapping time instead of at configuration time. In the converter, access the real source object's enum field directly. Update callers from mapper.addMappings(propertyMap) to mapper.addConverter(converter).
Before:
public static PropertyMap<MyEntity, MyDTO> entityMap = new PropertyMap<>() {
protected void configure() {
map().setName(source.getName());
String typeStr = source.getType().toString(); // NPE — proxy returns null for enum
map().setType(typeStr);
}
};
// Usage: mapper.addMappings(Mappings.entityMap);
After:
public static Converter<MyEntity, MyDTO> entityConverter = new AbstractConverter<>() {
protected MyDTO convert(MyEntity source) {
MyDTO dto = new MyDTO();
dto.setName(source.getName());
dto.setType(source.getType() != null ? source.getType().toString() : null);
// Map all other fields manually
return dto;
}
};
// Usage: mapper.addConverter(Mappings.entityConverter);
Cause: EclipseLink MOXy 4.x (used as the JSON provider via MOXyJsonProvider) requires Jakarta JSON Processing (JSON-P) API at runtime for JSON deserialization. The eclipselink all-in-one artifact does not transitively include jakarta.json-api. This manifests as ClassNotFoundException: jakarta.json.JsonException during JSON request/response processing (e.g., POST with JSON body).
Solution: Add both the Jakarta JSON-P API and an implementation to pom.xml:
<!-- Jakarta JSON Processing API + impl (needed by EclipseLink MOXy 4.x) -->
<dependency>
<groupId>jakarta.json</groupId>
<artifactId>jakarta.json-api</artifactId>
<version>2.1.3</version>
</dependency>
<dependency>
<groupId>org.eclipse.parsson</groupId>
<artifactId>parsson</artifactId>
<version>1.1.5</version>
</dependency>
Cause: Guice version doesn't support jakarta.servlet namespace.
Solution: Upgrade Guice to 7.x. See Step 2f above.
Cause: Library uses javax.servlet.* but the container provides jakarta.servlet.*.
Solution: Check for a Jakarta-compatible version. If not available, the library must be replaced or wrapped.
After completing this skill, proceed to the remaining feature skills: