SOAP Web services on WildFly made simple

TL;DR: Learn how to build, deploy, and configure modern SOAP Web Services using JAX-WS on WildFly 41.0.1.Final and Jakarta EE 11. This updated guide covers the migration from javax.* to jakarta.* packages, robust Apache CXF configuration (including timeouts and interceptors), secure deployments behind reverse proxies, and programmatic client testing using JUnit 5.

JAX-WS simplifies the development model for a web service endpoint a great deal. In short, an endpoint implementation bean is annotated with JAX-WS annotations and deployed to the server. The server automatically generates and publishes the abstract contract (i.e. WSDL + schema) for client consumption. All marshalling/unmarshalling is delegated to JAXB.

SOAP vs REST: Choosing the Right Tool

While REST has become the standard for lightweight APIs and mobile backends, SOAP remains highly relevant in enterprise-grade integrations. SOAP excels because of its strict contract enforcement (WSDL), built-in security standards (WS-Security), and reliability standards (WS-ReliableMessaging). Rather than a legacy protocol, SOAP represents a robust choice for complex transactional ecosystems, financial pipelines, and strict B2B integrations where strict schemas are non-negotiable.

You can follow two approaches for developing your SOAP web services:

  • Top-down: This approach, also known as contract-first, starts with the contract (the WSDL) by defining operations, messages, and schemas. When both the consumer and provider agree on the contract, you can then implement the Java classes based on that contract.
  • Bottom-up: This approach, also known as code-first, dictates that the WSDL file is generated directly from the Java programming interfaces. It is simpler to use for developers who do not have deep knowledge of the WSDL syntax and is the easiest choice if you want to turn your Java classes or EJBs into web services.

We will use the code-first approach; that is, we will start by creating a Java class and convert this into a Web service component. The Java class in turn can be either a simple POJO or an EJB component. We will start from a simple POJO class going through the following steps:

  • Create a Service Endpoint Interface (SEI) and define a business method to be used with the web service.
  • Create the implementation class and annotate it as a web service.

Coding the Service Interface

The Service Endpoint Interface (SEI) is an abstract interface which outlines the business methods available in our Web service. Notice that we are using the modern Jakarta EE 11 namespaces (jakarta.*):

package com.itbuzzpress.jaxws.ws;

import jakarta.jws.WebService;
import jakarta.jws.WebMethod;

@WebService
public interface AccountWSItf {
    public void newAccount(String name);

    @WebMethod
    public void withdraw(String name, long amount) throws RuntimeException;

    @WebMethod
    public void deposit(String name, long amount);

    @WebMethod
    public Account findAccountByName(String name);
}

It is noteworthy to observe the @WebService annotation. This annotation is really all it takes to leverage a Web service; when adding this annotation, the XML Web Services runtime will generate all the necessary plumbing for transforming a Java method invocation into an XML-over-HTTP call.

The @WebService annotation also comes with attributes that let you completely define a Web service in terms of the contract exposed via the WSDL file. For the moment we will ignore these attributes and proceed with our development.

The other annotation included is jakarta.jws.WebMethod, which is optional and provides the operation name and action elements used to customize the attribute names of the operation in the WSDL document. For the moment, we will rely on its default values.

The @WebMethod can also be used to restrict which methods of the Web service are visible to your clients. If it is not included, all Web service methods are available to Web service clients. On the other hand, if any method is annotated with @WebMethod, only those annotated methods will be exposed.

Our Web service interface references the Account class that is used as a data entity to transfer account attributes:

package com.itbuzzpress.jaxws.ws;

import jakarta.persistence.Entity;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.Id;
import jakarta.xml.bind.annotation.XmlRootElement;
import java.io.Serializable;

@Entity
@XmlRootElement
public class Account implements Serializable {
    private static final long serialVersionUID = 1L;

    @Id
    @GeneratedValue
    private Long id;

    private String name;
    private long amount;

    // Standard Getters/Setters
    public Long getId() { return id; }
    public void setId(Long id) { this.id = id; }
    public String getName() { return name; }
    public void setName(String name) { this.name = name; }
    public long getAmount() { return amount; }
    public void setAmount(long amount) { this.amount = amount; }
}

As you can see, we have added a @XmlRootElement annotation to the Account class. The @XmlRootElement is part of the Jakarta XML Binding (JAXB) library. JAXB provides a convenient way to map XML schema to a representation in Java code, handling the marshaling and unmarshaling behind the scenes without developers needing to construct manual XML DOM parsers. Therefore, the @XmlRootElement annotation maps the Account class directly to the XML root element in the SOAP payload.

Developing the Implementation Class

Having defined our contract, let’s check out the implementation class which wraps an AccountManager EJB:

package com.itbuzzpress.jaxws.ws;

import jakarta.inject.Inject;
import jakarta.jws.WebService;
import jakarta.jws.WebParam;
import jakarta.jws.WebResult;
import jakarta.jws.soap.SOAPBinding;

@WebService(endpointInterface = "com.itbuzzpress.jaxws.ws.AccountWSItf",
            serviceName = "AccountWSService",
            portName = "AccountWSPort")
@SOAPBinding(style = SOAPBinding.Style.RPC)
public class AccountWS implements AccountWSItf {
    @Inject
    private AccountManager ejb;

    @Override
    public void newAccount(@WebParam(name = "name") String name) {
        ejb.createAccount(name);
    }

    @Override
    public void withdraw(@WebParam(name = "name") String name,
                         @WebParam(name = "amount") long amount) throws RuntimeException {
        ejb.withdraw(name, amount);
    }

    @Override
    public void deposit(@WebParam(name = "name") String name,
                        @WebParam(name = "amount") long amount) {
        ejb.deposit(name, amount);
    }

    @Override
    @WebResult(name = "BankAccount")
    public Account findAccountByName(@WebParam(name = "name") String name) {
        return ejb.findAccount(name);
    }
}

Our service implementation class, named AccountWS, contains annotations specific to Jakarta XML Web Services. The @SOAPBinding annotation determines how the Web service is bound to the SOAP messaging protocol. There are two styles:

  • RPC (Remote Procedure Call): The SOAP message contains parameters and return values directly. The <soap:Body> carries an element representing the operation name, containing sub-elements for each parameter.
  • Document Style: The SOAP Body contains XML documents adhering to a defined XML Schema. It allows complete flexibility and is generally favored in complex schemas, though RPC is simpler for quick, straightforward method mapping.

Additionally, we use @jakarta.jws.WebParam to explicitely name parameters in the generated WSDL contract, and @jakarta.jws.WebResult to customize the output element name (defaulting to return if omitted).

Timeouts, Handlers, and Interceptors

When running SOAP in production on WildFly 41, configuring timeouts is crucial to prevent resource starvation. Standard JAX-WS allows configuration directly on the client request context:

import jakarta.xml.ws.BindingProvider;
import java.util.Map;

AccountWSItf ws = service.getPort(AccountWSItf.class);
Map<String, Object> requestContext = ((BindingProvider) ws).getRequestContext();
requestContext.put("jakarta.xml.ws.client.connectionTimeout", 5000); // 5 seconds
requestContext.put("jakarta.xml.ws.client.receiveTimeout", 15000);    // 15 seconds

If you need native, high-performance operations, it's important to understand the difference between handlers and interceptors:

  • JAX-WS Handlers: Portable, standard handlers (implementing jakarta.xml.ws.handler.Handler). While standard, they parse the SOAP payload into a DOM tree, introducing significant performance overhead.
  • Apache CXF Interceptors: Specific to the JBossWS-CXF reference stack. They operate via raw stream-level processing (StAX), making them highly efficient. CXF Interceptors allow you to hook directly into phases like RECEIVE, PRE_STREAM, or WRITE for tasks like logging, custom security, or packet manipulation.

Deploying the Web Service to WildFly 41

To compile and build this application targeting WildFly 41.0.1.Final and Jakarta EE 11, update your pom.xml to reference the modern dependency specifications:

<dependency>
    <groupId>jakarta.platform</groupId>
    <artifactId>jakarta.jakartaee-api</artifactId>
    <version>11.0.0</version>
    <scope>provided</scope>
</dependency>

Once you package and deploy your archive, the WildFly server log will report the endpoint registration using the subsystem manager:

[org.jboss.as.webservices] (MSC service thread 1-4) WFLYWS0018: Registering web service endpoint: AccountWS
[org.jboss.as.webservices] (MSC service thread 1-4) WFLYWS0019: Link to WSDL: http://localhost:8080/ee-ws-basic/AccountWS?wsdl

Creating a WS Test Client

Once the web service is published, you can access the generated WSDL at the URL: http://localhost:8080/ee-ws-basic/AccountWS?wsdl

From the WSDL file, we can write an automated integration test using JUnit 5. Add the following class to your src/test/java directory:

package com.itbuzzpress.jaxws.test;

import com.itbuzzpress.jaxws.ws.Account;
import com.itbuzzpress.jaxws.ws.AccountWSItf;
import org.junit.jupiter.api.Test;
import jakarta.xml.ws.Service;
import javax.xml.namespace.QName;
import java.net.URL;

import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertEquals;

public class TestWS {
    @Test
    public void testWS() {
        String name = "John Smith";
        try {
            URL url = new URL("http://localhost:8080/ee-ws-basic/AccountWS?wsdl");
            QName qname = new QName("http://ws.jaxws.itbuzzpress.com/", "AccountWSService");
            
            Service service = Service.create(url, qname);
            AccountWSItf ws = service.getPort(AccountWSItf.class);
            
            ws.newAccount(name);
            System.out.println("Created account name: " + name);
            
            ws.deposit(name, 1000);
            System.out.println("Deposit: $1000");
            
            ws.withdraw(name, 500);
            System.out.println("Withdraw: $500");
            
            Account account = ws.findAccountByName(name);
            assertNotNull(account);
            
            long money = account.getAmount();
            assertEquals(500L, money);
            System.out.println("Account balance is: " + account.getAmount());
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

Inside the test, the service.getPort() call dynamically initiates a client proxy using JBossWS-CXF execution libraries to transparently serialize/deserialize your RPC invocations.


Recommended Articles

Disable JAX-WS SOAP Web Services on WildFly Application Server - Comprehensive Guide

Learn how to disable JAX-WS SOAP Web Services at both application and subsystem levels in WildFly. #WildFly #Java #Middleware

Deploying SOAP Web Services in Jakarta EE Applications: A Step-by-Step Guide

Learn how to build and deploy SOAP-based web services in Jakarta EE applications, including guidance on the changes in Java SE that removed the JAX-WS API.

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.