Apache CXF Interceptors on WildFly 41 and Jakarta EE 11
Interceptors are the fundamental processing unit which is native in Apache CXF. Basically, they are Java classes that intercept your message to provide or apply certain core services to it. In this tutorial we will show how to use them and how to create custom ones targeting modern corporate infrastructure.
Apache CXF provides many built-in Interceptors that provide core services to the message that is being exchanged between consumer and service endpoint. These interceptors do the work of marshalling and unmarshalling, manipulating message headers, performing authorization checks, validating the message data, and so on.
You can also create your own Interceptors which are able to process incoming and outgoing messages. However, before doing that, you need to learn how Interceptors work:
Interceptors are invoked in chains and organized in phases. When a CXF client invokes a CXF server, there is an outgoing interceptor chain for the client and an incoming chain for the server. When the server sends the response back to the client, there is an outgoing chain for the server and an incoming one for the client. Additionally, in the case of SOAPFaults, a CXF web service will create a separate outbound error handling chain and the client will create an inbound error handling chain.

JAX-WS Handlers vs. Apache CXF Interceptors
When developing SOAP web services in Jakarta EE 11, it is important to distinguish between standard Jakarta XML Web Services (JAX-WS) Handlers and Apache CXF Interceptors:
- JAX-WS Handlers (Standard): Standardized via
jakarta.xml.ws.handler.Handler(includingLogicalHandlerandSOAPHandler). They are completely portable across Jakarta EE compliant application servers (such as WildFly, GlassFish, or Open Liberty), but they operate strictly at the object model level and lack transport-specific performance optimizations. - Apache CXF Interceptors (CXF-Specific): Highly optimized and proprietary to the Apache CXF stack. They are organized in structured Phases (e.g.,
RECEIVE,PRE_MARSHAL,SEND). They offer raw access to transport streams, XML message headers, and can perform ultra-low-level manipulation of the message stream before it is unmarshalled, making them ideal for high-performance enterprise extensions.
Writing a Basic Interceptor
Writing an interceptor is relatively simple. Your interceptor needs to extend from either the AbstractPhaseInterceptor<Message> or one of its subclasses such as AbstractSoapInterceptor.
package com.sample.ws;
import org.apache.cxf.interceptor.Fault;
import org.apache.cxf.message.Message;
import org.apache.cxf.phase.AbstractPhaseInterceptor;
import org.apache.cxf.phase.Phase;
public class MyPhaseInterceptor extends AbstractPhaseInterceptor<Message> {
public MyPhaseInterceptor() {
super(Phase.INVOKE); // Put this interceptor in the INVOKE phase
}
@Override
public void handleMessage(Message msg) throws Fault {
// Core business logic to process or manipulate the message
}
}
The most important thing you should notice is the constructor: it defines the phase name for your interceptor. When you specify the phase, your interceptor is ordered inside the execution chain according to that phase.
Most of the time you will want to extend from sub-classes of AbstractPhaseInterceptor. For example, using the AbstractSoapInterceptor you can access more specific information such as the SOAP Header.
For example, the following Interceptor scans through the SOAP header:
package com.sample.ws;
import java.util.List;
import org.apache.cxf.binding.soap.SoapMessage;
import org.apache.cxf.binding.soap.interceptor.AbstractSoapInterceptor;
import org.apache.cxf.headers.Header;
import org.apache.cxf.interceptor.Fault;
import org.apache.cxf.phase.Phase;
public class CustomHeaderVerificationInterceptor extends AbstractSoapInterceptor {
public CustomHeaderVerificationInterceptor() {
super(Phase.PRE_PROTOCOL);
}
@Override
public void handleMessage(SoapMessage message) throws Fault {
List<Header> list = message.getHeaders();
for (Header header : list) {
System.out.println("Found Header: " + header.getName() + " = " + header.getObject());
}
}
}
You can also add information to the SOAP header using the message.getHeaders().add(header) method of the SoapMessage class:
package com.sample.ws;
import javax.xml.namespace.QName;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.apache.cxf.binding.soap.SoapMessage;
import org.apache.cxf.binding.soap.interceptor.AbstractSoapInterceptor;
import org.apache.cxf.headers.Header;
import org.apache.cxf.interceptor.Fault;
import org.apache.cxf.phase.Phase;
public class BasicAuthHeaderInterceptor extends AbstractSoapInterceptor {
public BasicAuthHeaderInterceptor() {
super(Phase.WRITE);
}
@Override
public void handleMessage(SoapMessage message) throws Fault {
DocumentBuilder builder;
try {
builder = DocumentBuilderFactory.newInstance().newDocumentBuilder();
} catch (ParserConfigurationException e) {
throw new Fault(e);
}
Document doc = builder.newDocument();
Element eSecure = doc.createElement("Secured");
Element eUser = doc.createElement("user");
eUser.setTextContent("myuser");
Element ePassword = doc.createElement("password");
ePassword.setTextContent("password");
eSecure.appendChild(eUser);
eSecure.appendChild(ePassword);
// Create Header object with Namespace-qualified QName
QName qnameCredentials = new QName("https://mastertheboss.com/security", "Secured");
Header header = new Header(qnameCredentials, eSecure);
message.getHeaders().add(header);
}
}
In this example we have just added this header to the SOAP Envelope:
<soap:Header>
<Secured xmlns="https://mastertheboss.com/security">
<user>myuser</user>
<password>password</password>
</Secured>
</soap:Header>
A Note on Logging and Message Payload Tracing
Warning: In Apache CXF 4.x (used by WildFly 41), the legacy AbstractLoggingInterceptor class has been completely removed. Logging should now be performed either using the modern CXF Logging Feature (org.apache.cxf.ext.logging.LoggingFeature) or via a custom phase interceptor utilizing CachedOutputStream directly to safely inspect incoming/outgoing payloads:
package com.sample.ws;
import java.io.InputStream;
import org.apache.cxf.helpers.IOUtils;
import org.apache.cxf.interceptor.Fault;
import org.apache.cxf.io.CachedOutputStream;
import org.apache.cxf.message.Message;
import org.apache.cxf.phase.AbstractPhaseInterceptor;
import org.apache.cxf.phase.Phase;
public class ModernLogInterceptor extends AbstractPhaseInterceptor<Message> {
public ModernLogInterceptor() {
super(Phase.RECEIVE);
}
@Override
public void handleMessage(Message message) throws Fault {
InputStream is = message.getContent(InputStream.class);
if (is != null) {
try (CachedOutputStream bos = new CachedOutputStream()) {
IOUtils.copy(is, bos);
bos.flush();
is.close();
// Restore the stream for the rest of the interceptor chain
message.setContent(InputStream.class, bos.getInputStream());
System.out.println("============= SOAP Message Payload =============");
bos.writeCacheTo(System.out);
System.out.println("\n================================================");
} catch (Exception e) {
throw new Fault(e);
}
}
}
}
Adding Interceptors to your Web Services
There are two primary ways to apply CXF Interceptors to your deployments in modern environments:
1) Descriptor Configuration (Recommended)
The standard enterprise approach is to define configurations within a deployment descriptor file. This separates implementation from infrastructure settings. You can include a jaxws-endpoint-config.xml descriptor in the WEB-INF folder of your WAR application (or META-INF for an EJB JAR):
<?xml version="1.0" encoding="UTF-8"?>
<jaxws-config xmlns="urn:jboss:jbossws-jaxws-config:5.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="urn:jboss:jbossws-jaxws-config:5.0 https://www.jboss.org/schema/jbossws/jbossws-jaxws-config_5_0.xsd">
<endpoint-config>
<config-name>SampleWSConfig</config-name>
<property>
<property-name>cxf.interceptors.in</property-name>
<property-value>com.sample.ws.ModernLogInterceptor</property-value>
</property>
</endpoint-config>
</jaxws-config>
To associate this configuration class with your Jakarta SOAP Web Service implementation, use the JBossWS proprietary annotation @org.jboss.ws.api.annotation.EndpointConfig:
package com.sample.ws;
import jakarta.jws.WebService;
import org.jboss.ws.api.annotation.EndpointConfig;
@WebService(endpointInterface = "com.sample.ws.SayHi")
@EndpointConfig(configFile = "WEB-INF/jaxws-endpoint-config.xml", configName = "SampleWSConfig")
public class SayHiImpl implements SayHi {
@Override
public long sayHi(long arg) {
return arg;
}
}
As a global alternative, you can set the same property system-wide inside the WildFly webservices subsystem of your standalone.xml configuration file:
<subsystem xmlns="urn:jboss:domain:webservices:3.0">
<wsdl-host>${jboss.bind.address:127.0.0.1}</wsdl-host>
<endpoint-config name="Standard-Endpoint-Config">
<property name="cxf.interceptors.in" value="com.sample.ws.ModernLogInterceptor"/>
</endpoint-config>
</subsystem>
2) Using CXF Annotations (Code-Centric Approach)
If you do not want to manage separate descriptors, CXF provides annotations that declare interceptor registration directly on your service class. This works out-of-the-box on WildFly 41 with the JBossWS-CXF stack:
package com.sample.ws;
import jakarta.jws.WebService;
import org.apache.cxf.interceptor.InInterceptors;
import org.apache.cxf.interceptor.OutInterceptors;
@InInterceptors(interceptors = {"com.sample.ws.ModernLogInterceptor"})
@OutInterceptors(interceptors = {"com.sample.ws.BasicAuthHeaderInterceptor"})
@WebService(endpointInterface = "com.sample.ws.SayHi")
public class SayHiImpl implements SayHi {
@Override
public long sayHi(long arg) {
return arg;
}
}
Legacy Spring XML configuration (e.g. cxf.xml using Spring <beans>) is no longer recommended for deployment inside modern WildFly containers, as modern versions do not ship with an integrated Spring subsystem and favor standard Jakarta EE CDI and configuration descriptors.
Adding Fault Interceptors to your Web Services
When a SOAP fault occurs, JAX-WS applications instantiate an outbound fault chain. The final interceptor on this chain (such as Soap11FaultOutInterceptor) serializes the exception. To modify these details—for example, to mask database connection stacktraces from leaking to untrusted clients—you can write a custom OutFaultInterceptor:
package com.sample.ws;
import org.apache.cxf.binding.soap.SoapMessage;
import org.apache.cxf.binding.soap.interceptor.AbstractSoapInterceptor;
import org.apache.cxf.interceptor.Fault;
import org.apache.cxf.phase.Phase;
public class CustomFaultInterceptor extends AbstractSoapInterceptor {
public CustomFaultInterceptor() {
super(Phase.MARSHAL);
}
@Override
public void handleMessage(SoapMessage message) throws Fault {
Fault fault = (Fault) message.getContent(Exception.class);
if (fault != null) {
// Mask detailed stacktrace details in production
fault.setMessage("A secure transaction fault occurred. Reference ID: 99485");
}
}
}
Bind the fault handler to your service using annotations:
package com.sample.ws;
import jakarta.jws.WebService;
import org.apache.cxf.interceptor.OutFaultInterceptors;
@OutFaultInterceptors(interceptors = {"com.sample.ws.CustomFaultInterceptor"})
@WebService(endpointInterface = "com.sample.ws.SayHi")
public class SayHiImpl implements SayHi {
@Override
public long sayHi(long arg) {
return arg;
}
}
Frequently Asked Questions (FAQs)
Q1: Why does compilation fail on WildFly 41 when importing javax.xml.ws.* or javax.jws.*?
WildFly 41 targets Jakarta EE 11, which has completely moved away from the older javax.* namespace. You must refactor your source code to import from jakarta.xml.ws.* and jakarta.jws.*. CXF-specific classes remain under the org.apache.cxf.* namespace.
Q2: What is the recommended way to secure CXF services without sending plain passwords?
Do not pass plaintext credentials over standard HTTP or custom HTTP Basic setups. We highly recommend routing all SOAP communication through HTTPS/TLS, combined with WS-Security (using the WSS4J Interceptors configured inside JBossWS-CXF) or passing OAuth2 tokens in the HTTP Authorization headers.
Q3: Can I use standard JAX-WS handlers alongside CXF Interceptors?
Yes. JAX-WS Handlers defined using @HandlerChain will be mapped and run inside CXF's interceptor pipeline automatically. CXF executes JAX-WS Logical and SOAP handlers within specific internal phases, ensuring compatibility and compliance with standard Jakarta specifications.
Recommended Articles
Developing SOAP Web Services on WildFly 41 (Jakarta EE 11) with Apache CXF
Learn how to develop, deploy, and secure Jakarta XML Web Services (SOAP) on WildFly 41.0.1.Final and Jakarta EE 11 using Apache CXF, WS-Security, and modern testing tools.
How to develop CXF Web services with JBoss / WildFly
Learn how to develop, deploy, and secure SOAP Web Services on WildFly 41.0.1.Final using Jakarta EE 11 and Apache CXF. Master JAX-WS handlers, CXF interceptors, and robust cloud configurations.
Enhance Your Enterprise Java Applications with HTTP Basic Authentication and Apache CXF Interceptors
Learn how to implement HTTP Basic Authentication for JAX-WS Web services and explore alternative authentication mechanisms using Apache CXF Interceptors. #Java #Middleware #CloudNative
Using Axis Web Services with JBoss: Migration to modern JAX-WS with WildFly 41
Learn how to migrate legacy Apache Axis SOAP web services from JBoss to modern Jakarta EE 11 and JAX-WS on WildFly 41.0.1.Final using Apache CXF with secure HTTPS configurations and debugging.