How to test SOAP Web Services

TL;DR: Updated for WildFly 41.0.1.Final and Jakarta EE 11. This hands-on tutorial demonstrates how to test modern jakarta.xml.ws services programmatically using JUnit 5 and Apache CXF, as well as with industry-standard tools like SoapUI, JMeter, and cURL. Learn how to configure timeouts, secure connections via TLS, and manage complex routing behind cloud proxies.

This tutorial will teach how to test SOAP Web Services using open source testing tools or Java testing frameworks on modern Jakarta EE runtime environments.

SOAP vs REST: Choosing the Right Integration Path

While RESTful APIs utilizing JSON are widespread for modern mobile and lightweight web services, SOAP (Simple Object Access Protocol) remains a vital standard in enterprise environments. SOAP is uniquely suited for systems requiring strict interface contracts (via WSDL), built-in security profiles (WS-Security), and distributed transactions (WS-AtomicTransaction). Understanding how to efficiently test, debug, and secure SOAP endpoints remains an essential skill for enterprise Java developers.

Coding our SOAP Web Service

Let’s start by defining our Jakarta XML Web Services (JAX-WS) contract using the modern jakarta.* namespace:

package com.mastertheboss.jaxws;

import jakarta.jws.WebMethod;
import jakarta.jws.WebParam;
import jakarta.jws.WebResult;
import jakarta.jws.WebService;

@WebService
public interface AccountWSItf {
    @WebMethod
    public String newAccount(@WebParam(name = "name") String name);

    @WebMethod
    public String withdraw(@WebParam(name = "name") String name, @WebParam(name = "amount") long amount) throws RuntimeException;

    @WebMethod
    public String deposit(@WebParam(name = "name") String name, @WebParam(name = "amount") long amount);

    @WebMethod
    @WebResult(name = "BankAccount")
    public Account findAccountByName(@WebParam(name = "name") String name);
}

This is the SOAP Web Service implementation class leveraging CDI injection and JAX-WS annotations, configured to run on WildFly 41:

package com.mastertheboss.jaxws;

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

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

    @Override
    public String newAccount(@WebParam(name = "name") String name) {
        ejb.createAccount(name);
        return "Created Account " + name;
    }

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

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

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

SOAP Web Service Testing with SoapUI

In order to test a Web Service we need to collect the WSDL URL of the Web service. This tutorial discusses how you can get the URL of a SOAP Web Service on WildFly: How do I know the WSDL URL of my Web Service ?

The recommended graphical option for Web Service Testing is SoapUI which is available in an OpenSource version and in an Enterprise version called ReadyAPI. You can download SoapUI from: https://www.soapui.org/downloads/soapui/

Once installed, it’s really simple to perform Web Service testing: select the New SOAP Project option from the File menu, which will prompt the following dialog:

soap web services testing

Enter the project name and the WSDL address of your SOAP Web service. SoapUI will import the WSDL and generate a default request for ad-hoc testing and exploring of services:

soap web services testing

So for example, to test the “newAccount” action you only have to add the arguments in the SOAP payload:

<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" xmlns:ws="http://ws.jaxws.mastertheboss.com/">
   <soapenv:Header/>
   <soapenv:Body>
      <ws:newAccount>
         <!--Optional:-->
         <arg0>JohnSmith</arg0>
      </ws:newAccount>
   </soapenv:Body>
</soapenv:Envelope>

Then, submit the request to check the Web Service response:

<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
   <soap:Body>
      <ns2:newAccountResponse xmlns:ns2="http://ws.jaxws.mastertheboss.com/">
         <return>Created Account JohnSmith</return>
      </ns2:newAccountResponse>
   </soap:Body>
</soap:Envelope>

SOAP Web Service Testing with cURL

If you want to test your Web Services in a pure and simple bash script, cURL is an extremely handy lightweight option. When testing APIs, always ensure you route requests over HTTPS in production to protect sensitive data payloads. It’s a good idea to have the SOAP XML message generated by SoapUI saved to a file, such as "soap.xml". Then you can run cURL to perform the test:

curl --insecure --header "Content-Type: text/xml;charset=UTF-8" --data @soap.xml https://localhost:8443/ee-ws-basic/AccountWS

On the other hand, if you don’t have a tool to generate the SOAP XML message to be sent, you can start from a basic template like this one, specifying the service details:

  • The NAMESPACE (e.g. “http://ws.jaxws.mastertheboss.com/”)
  • The OPERATION name
  • The list of ARGUMENTS (if any)
<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" xmlns:sam="http://ws.jaxws.mastertheboss.com/">
   <soapenv:Header/>
   <soapenv:Body>
      <sam:newAccount>          
         <arg0>AAA</arg0>         
      </sam:newAccount>
   </soapenv:Body>
</soapenv:Envelope>

SOAP Web Service Testing with JMeter

If you need to perform performance testing or stress test your web services, you can use Apache JMeter to construct a SOAP Web Service Test Plan. You can download JMeter from https://jmeter.apache.org/download_jmeter.cgi

Launch JMeter and choose from the menu File → Templates… and select template “Building a SOAP Webservice Test Plan”.

soap web services testing

Then, click the “Create” button. The following Test plan will be created:

soap web services testing

Select the HTTP Request and set the server name/port, the Path, and the Body Data as you can see from the following picture:

soap web services testing

Finally, select the HTTP Header Manager and set the SOAPAction header. For modern SOAP 1.1 setups, this can be specified or kept empty if already detailed in the payload context:

soap web services testing

Finally, configure the Thread Group to simulate the desired concurrent users, ramp-up time, and loop counts, then execute the Test Plan to inspect the results within the Listener interfaces.

Testing your SOAP Web Service Programmatically

In modern Java EE environments like WildFly 41, programmatic testing is implemented using JUnit 5. We will look at two main programmatic clients:

  • Using the standard JAX-WS jakarta.xml.ws.Service API to dynamically create a client proxy.
  • Using the native JaxWsProxyFactoryBean from Apache CXF (the default web services stack on WildFly 41). This allows you to leverage CXF interceptors and fine-tune connection features.

Differences: JAX-WS Handlers vs. CXF Interceptors

When customizing client requests (logging, WS-Security, header injection), you should understand the distinction between Handlers and Interceptors:

  • JAX-WS Handlers: Defined by the Jakarta EE specification, making them portable across application servers (WildFly, Open Liberty, WebSphere). They process logical messages or raw SOAP structures.
  • Apache CXF Interceptors: Unique to Apache CXF. They are structured in chain phases (e.g., RECEIVE, PRE_STREAM, WRITE) and are much more powerful, as they can access raw streams before the XML model is fully processed, resulting in superior performance.

Automated Java Test Case (JUnit 5)

Below is a modern automated test utilizing standard JAX-WS capabilities, including timeout configuration, and native Apache CXF implementations:

package com.mastertheboss.jaxws.test;

import com.mastertheboss.jaxws.Account;
import com.mastertheboss.jaxws.AccountWSItf;
import jakarta.xml.ws.Service;
import jakarta.xml.ws.BindingProvider;
import javax.xml.namespace.QName;
import java.net.URL;
import java.util.Map;

import org.apache.cxf.jaxws.JaxWsProxyFactoryBean;
import org.apache.cxf.ext.logging.LoggingInInterceptor;
import org.apache.cxf.ext.logging.LoggingOutInterceptor;

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

public class TestWS {

    @Test
    public void testStandardJAXWSClient() {
        String name = "John Smith";
        try {
            URL url = new URL("http://localhost:8080/ee-ws-basic/AccountWS?wsdl");
            QName qname = new QName("http://ws.jaxws.mastertheboss.com/", "AccountWSService");

            Service service = Service.create(url, qname);
            AccountWSItf ws = service.getPort(AccountWSItf.class);

            // Configuring connection timeouts using Jakarta standard properties
            Map<String, Object> requestContext = ((BindingProvider) ws).getRequestContext();
            requestContext.put("jakarta.xml.ws.client.connectionTimeout", 5000); // 5s connection timeout
            requestContext.put("jakarta.xml.ws.client.receiveTimeout", 10000);   // 10s receive timeout

            ws.newAccount(name);
            System.out.println("Created account name " + name);
            ws.deposit(name, 1000);
            ws.withdraw(name, 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();
            throw new RuntimeException(e);
        }
    }

    @Test
    public void testApacheCXFClient() {
        String name = "Adam Smith";
        System.out.println("TEST Using Apache CXF native API and Interceptors");

        JaxWsProxyFactoryBean factory = new JaxWsProxyFactoryBean();
        factory.setServiceClass(AccountWSItf.class);
        factory.setAddress("http://localhost:8080/ee-ws-basic/AccountWS");

        // Modern CXF logging interceptors
        factory.getInInterceptors().add(new LoggingInInterceptor());
        factory.getOutInterceptors().add(new LoggingOutInterceptor());

        AccountWSItf ws = (AccountWSItf) factory.create();
        
        ws.newAccount(name);
        System.out.println("Created account name " + name);
        ws.deposit(name, 1000);
        ws.withdraw(name, 500);

        Account account = ws.findAccountByName(name);
        assertNotNull(account);
        long money = account.getAmount();
        assertEquals(500L, money);
        System.out.println("Account balance is " + account.getAmount());
    }
}

Deploying to Cloud Environments (Kubernetes & OpenShift)

When running WildFly 41 containers within cloud environments behind reverse proxies or ingress controllers (e.g., NGINX Ingress, Traefik, HAProxy) which terminate SSL, the dynamic WSDL generated by CXF might mistakenly contain internal HTTP links instead of the external HTTPS host names. To resolve this, configure the webservices subsystem using WildFly CLI commands:

/subsystem=webservices:write-attribute(name=wsdl-host, value="api.mastertheboss.com")
/subsystem=webservices:write-attribute(name=wsdl-secure-port, value=443)
/subsystem=webservices:write-attribute(name=wsdl-uri-scheme, value="https")
reload

You can check the full source code of these examples here: https://github.com/fmarchioni/mastertheboss/tree/master/jax-ws/ee-ws-basic

Frequently Asked Questions (FAQs)

Q1: Why do I get a ClassNotFoundException/NoClassDefFoundError with javax.xml.ws.Service in WildFly 41?
A: WildFly 41 implements Jakarta EE 11, which uses the jakarta.* namespace. You must replace all imports of javax.xml.ws.*, javax.jws.*, and javax.xml.bind.* with their jakarta.* equivalents in your code and dependency trees.

Q2: How should client timeouts be configured within the modern JAX-WS API?
A: Timeout configuration can be managed dynamically by casting your web service client port to jakarta.xml.ws.BindingProvider, and modifying the request context map using the standard properties jakarta.xml.ws.client.connectionTimeout and jakarta.xml.ws.client.receiveTimeout.

Q3: How do I handle authentication securely when testing SOAP APIs?
A: Never use plain-text Basic Authentication over unencrypted HTTP. Always ensure the endpoint is exposed over HTTPS (TLS) and configure WS-Security policies or standard HTTPS client-side certificates if transport or message-level encryption is required.


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.

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.

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.

Creating SOAP Messages Programmatically with Jakarta EE 11 and WildFly 41

Master programmatic SOAP message creation and parsing using SAAJ (Jakarta SOAP with Attachments 3.0) on WildFly 41.0.1.Final and Jakarta EE 11. Complete code examples included.