Description
We recently undertook a major technology stack upgrade, migrating from:
- JDK 8 → JDK 21
- Spring 3 → Spring 6.x
- WebSphere (WAS) → Tomcat 10
- CXF → CXF 4.x
During this migration, we encountered the following error:
No qualifying bean of type 'jakarta.xml.ws.WebServiceContext' available
Root Cause
After extensive investigation, we discovered that Spring 6 removed the built-in JAX-WS support that was previously available in Spring 5 and earlier versions. This change wasn't prominently documented in the Spring 6 migration guide, making it a hidden pitfall for teams upgrading legacy applications.
The issue occurs because:
- Spring's
CommonAnnotationBeanPostProcessor
tries to injectWebServiceContext
as a regular Spring bean -
WebServiceContext
is not a Spring-managed bean - it should be handled by the JAX-WS runtime (CXF in our case) - Spring 6 no longer automatically excludes
WebServiceContext
from dependency injection
Research and References
During our investigation, we found several relevant resources:
- Spring6 for JSR 250 annotations isn't disabled for jakarta.xml.ws.WebServiceContext
- "No qualifying bean of type 'jakarta.xml.ws.WebServiceContext' available" migrating to EAP 8.0.5
- Spring Boot 3 Update: No qualifying bean of type 'jakarta.xml.ws.WebServiceContext' available
Key insights from these resources:
Solution
For Spring Framework Applications (Our Case)
Since we're using Spring Framework without Spring Boot, we adapted the CxfJaxwsAutoConfiguration from Spring Boot for our application.
Here's how Spring Boot handles it:
@Configuration
@ConditionalOnClass({ Resource.class, WebServiceContext.class })
public class CxfJaxwsAutoConfiguration {
@Bean
static BeanFactoryPostProcessor jaxwsBeanFactoryPostProcessor() {
return new BeanFactoryPostProcessor() {
@Override
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
final var beans = beanFactory.getBeansOfType(CommonAnnotationBeanPostProcessor.class, true, false);
// The {@code javax.xml.ws.WebServiceContext} interface should be ignored since it will
// be resolved by the CXF's JAX-WS runtime.
beans.forEach((name, bean) -> bean.ignoreResourceType(WebServiceContext.class.getName()));
}
};
}
}
Our Implementation
We created a custom configuration class for our Spring Framework application:
package com.yourcompany.config;
import jakarta.annotation.Resource;
import jakarta.xml.ws.WebServiceContext;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.BeanFactoryPostProcessor;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.CommonAnnotationBeanPostProcessor;
import org.springframework.context.annotation.Configuration;
@Configuration
public class CxfJaxwsConfiguration {
@Bean
public static BeanFactoryPostProcessor jaxwsBeanFactoryPostProcessor() {
return new BeanFactoryPostProcessor() {
@Override
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
// Check if the required classes are available
if (!isClassPresent("jakarta.annotation.Resource") ||
!isClassPresent("jakarta.xml.ws.WebServiceContext")) {
return;
}
final var beans = beanFactory.getBeansOfType(CommonAnnotationBeanPostProcessor.class, true, false);
// Tell Spring to ignore WebServiceContext for dependency injection
// CXF's JAX-WS runtime will handle it instead
beans.forEach((name, bean) -> bean.ignoreResourceType(WebServiceContext.class.getName()));
}
};
}
private static boolean isClassPresent(String className) {
try {
Class.forName(className, false, CxfJaxwsConfiguration.class.getClassLoader());
return true;
} catch (ClassNotFoundException e) {
return false;
}
}
}
How It Works
- The
BeanFactoryPostProcessor
runs early in the Spring context initialization - It finds all
CommonAnnotationBeanPostProcessor
instances - It instructs them to ignore
WebServiceContext
when processing@Resource
annotations - This allows CXF's JAX-WS runtime to properly inject
WebServiceContext
instead of Spring trying to find it as a bean
I hope this helps others facing the same issue during their Spring 6 migration!
Top comments (0)