DZone
Thanks for visiting DZone today,
Edit Profile
  • Manage Email Subscriptions
  • How to Post to DZone
  • Article Submission Guidelines
Sign Out View Profile
  • Post an Article
  • Manage My Drafts
Over 2 million developers have joined DZone.
Log In / Join
Please enter at least three characters to search
Refcards Trend Reports
Events Video Library
Refcards
Trend Reports

Events

View Events Video Library

Zones

Culture and Methodologies Agile Career Development Methodologies Team Management
Data Engineering AI/ML Big Data Data Databases IoT
Software Design and Architecture Cloud Architecture Containers Integration Microservices Performance Security
Coding Frameworks Java JavaScript Languages Tools
Testing, Deployment, and Maintenance Deployment DevOps and CI/CD Maintenance Monitoring and Observability Testing, Tools, and Frameworks
Culture and Methodologies
Agile Career Development Methodologies Team Management
Data Engineering
AI/ML Big Data Data Databases IoT
Software Design and Architecture
Cloud Architecture Containers Integration Microservices Performance Security
Coding
Frameworks Java JavaScript Languages Tools
Testing, Deployment, and Maintenance
Deployment DevOps and CI/CD Maintenance Monitoring and Observability Testing, Tools, and Frameworks

The software you build is only as secure as the code that powers it. Learn how malicious code creeps into your software supply chain.

Apache Cassandra combines the benefits of major NoSQL databases to support data management needs not covered by traditional RDBMS vendors.

Generative AI has transformed nearly every industry. How can you leverage GenAI to improve your productivity and efficiency?

Modernize your data layer. Learn how to design cloud-native database architectures to meet the evolving demands of AI and GenAI workloads.

Related

  • Consuming SOAP Service With Apache CXF and Spring
  • Spring Boot - How To Use Native SQL Queries | Restful Web Services
  • RESTful Web Services: How To Create a Context Path for Spring Boot Application or Web Service
  • How To Validate HTTP Post Request Body - Restful Web Services With Spring Framework | Spring Boot

Trending

  • Endpoint Security Controls: Designing a Secure Endpoint Architecture, Part 2
  • Unit Testing Large Codebases: Principles, Practices, and C++ Examples
  • Automating Data Pipelines: Generating PySpark and SQL Jobs With LLMs in Cloudera
  • Detection and Mitigation of Lateral Movement in Cloud Networks
  1. DZone
  2. Coding
  3. Frameworks
  4. SOAP Web Services With Apache CXF and Spring Boot

SOAP Web Services With Apache CXF and Spring Boot

In this post, we build a SOAP Web Service from scratch using Apache CXF and Spring Boot.

By 
Brian Hannaway user avatar
Brian Hannaway
·
Jun. 20, 19 · Tutorial
Likes (7)
Comment
Save
Tweet
Share
84.1K Views

Join the DZone community and get the full member experience.

Join For Free

This post is based on one I wrote a few years ago about building contract first web services with Apache CXF and Spring. The previous post didn't use Spring Boot and most of the Spring and CXF configuration was via XML. This post moves things forward a bit by using the latest version of CXF and Spring Boot.

Sample App

We're going to build a simple Spring Boot app that exposes SOAP web service using Apache CXF. The service will have a single operation that takes an account number and returns bank account details. If you're impatient and want to jump ahead you can grab the full source code from GitHub.

Defining the Data Model

When building web services I always take a contract first approach. That means defining the service contract as a WSDL before writing the service implementation. We'll begin that process by creating an XSD file with the types that the Account Service will use. The first type we'll create is Account — the diagram below shows an XSD snippet for Account as well as a visual representation taken from XML Spy. Note: you don't need XML Spy to define XSDs but a visual editor can be very handy if you're designing complex domain models.

Account type

Next, we'll define a request type for the service that encapsulates service parameters. In this case, we have only one parameter, accountNumber, but its good practice to define a request type as its a nice way of wrapping multiple parameters and keeps the interface clean.

AccountDetailsRequest Type

Finally, we'll define the response type AccountDetailsResponse which is a simple wrapper for Account we created earlier. Again, we could have simply returned Account but I think it's generally a good idea to use wrappers for the response type as it means that we can easily add other data to the response wrapper in the future.

The full schema definition is shown below.

<?xml version="1.0" encoding="UTF-8"?>
<xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns="http://com/blog/samples/webservices/accountservice" xmlns:account="http://webservices.samples.blog.com" targetNamespace="http://com/blog/samples/webservices/accountservice" elementFormDefault="qualified">
 <xsd:complexType name="Account">
  <xsd:sequence>
   <xsd:element name="AccountNumber" type="xsd:string"/>
   <xsd:element name="AccountName" type="xsd:string"/>
   <xsd:element name="AccountBalance" type="xsd:double"/>
   <xsd:element name="AccountStatus" type="EnumAccountStatus"/>
  </xsd:sequence>
 </xsd:complexType> 
 <xsd:simpleType name="EnumAccountStatus">
  <xsd:restriction base="xsd:string">
   <xsd:enumeration value="Active"/>
   <xsd:enumeration value="Inactive"/>
  </xsd:restriction>
 </xsd:simpleType>
 <xsd:element name="AccountDetailsRequest">
  <xsd:complexType>
   <xsd:sequence>
    <xsd:element name="accountNumber" type="xsd:string"/>
   </xsd:sequence>
  </xsd:complexType>
 </xsd:element>
 <xsd:element name="AccountDetailsResponse">
  <xsd:complexType>
   <xsd:sequence>
    <xsd:element name="AccountDetails" type="Account"/>
   </xsd:sequence>
  </xsd:complexType>
 </xsd:element>
</xsd:schema>

Defining the Service WSDL

Now that we've defined the service types via XSD, its time to create a WSDL to define the public facing service contract. A WSDL is an XML document that describes a SOAP Web Service and how clients can interact with it. The Account Service WSDL is defined as follows.

<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<wsdl:definitions xmlns:soap="http://schemas.xmlsoap.org/wsdl/soap12/"
 xmlns:tns="http://www.briansjavablog.com/Accounts/" xmlns:wsdl="http://schemas.xmlsoap.org/wsdl/"
 xmlns:xsd="http://www.w3.org/2001/XMLSchema" name="Accounts"
 targetNamespace="http://www.briansjavablog.com/Accounts/"
 xmlns:accounts="http://com/blog/samples/webservices/accountservice">
 <wsdl:types>
  <xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema">
   <xsd:import namespace="http://com/blog/samples/webservices/accountservice"
    schemaLocation="../schema/AccountsService.xsd">
   </xsd:import>
  </xsd:schema>
 </wsdl:types>
 <wsdl:message name="AccountDetailsRequest">
  <wsdl:part element="accounts:AccountDetailsRequest" name="parameters" />
 </wsdl:message>
 <wsdl:message name="AccountDetailsResponse">
  <wsdl:part element="accounts:AccountDetailsResponse" name="parameters" />
 </wsdl:message>
 <wsdl:portType name="Accounts">
  <wsdl:operation name="GetAccountDetails">
   <wsdl:input message="tns:AccountDetailsRequest" />
   <wsdl:output message="tns:AccountDetailsResponse" />
  </wsdl:operation>
 </wsdl:portType>
 <wsdl:binding name="AccountsServiceSoapBinding" type="tns:Accounts">
  <soap:binding style="document"
   transport="http://schemas.xmlsoap.org/soap/http" />
  <wsdl:operation name="GetAccountDetails">
   <soap:operation
    soapAction="http://www.briansjavablog.com/Accounts/GetAccountDetails" />
   <wsdl:input>
    <soap:body use="literal" />
   </wsdl:input>
   <wsdl:output>
    <soap:body use="literal" />
   </wsdl:output>
  </wsdl:operation>
 </wsdl:binding>
 <wsdl:service name="AccountsService">
  <wsdl:port binding="tns:AccountsServiceSoapBinding" name="AccountsPort">
   <soap:address
    location="http://localhost:8080/apache-cfx-demo/services/accounts" />
  </wsdl:port>
 </wsdl:service>
</wsdl:definitions>

The WSDL contains 5 key pieces of information:

  • Types - <wsdl:types> defines the domain model used by the service. The model is defined via XSD and can be included inline, in the WSDL or imported from a separate XSD. Line 9 above imports the XSD file we created earlier.
  • Message - <wsdl:message> defines the request and response messages used by the service. The nested <wsdl:part> section defines the domain types of the request and response messages.
  • PortType - <wsdl:portType> defines the service operations, parameters, and response types exposed to clients.
  • Binding - <wsdl:binding> defines the protocol and data format.
    • The binding type attribute refers to the portType defined earlier in the WSDL.
    • The soap binding style can be either RPC or document.
    • The transport attribute indicates that the service will be exposed over HTTP. Other options (less common) include JMS and SMTP.
    • The operation element defines each operation that we exposed through the portType.
    • Binding - <wsdl:binding> defines the protocol and data format.
  • Service - <wsdl:service> defines the exposed service using the portType and binding we defined above.

Generating the Service Interface and Domain Model

At this point, we have a WSDL that defines a contract for the service we're going to build. The next step is to use the WSDL to generate Java classes for the domain model and service interface. We're going to use the CXF codegen plugin to run a WSDL2Java job as part of the build. The codegen plugin is defined in the project POM as follows.

<plugin>
  <groupId>org.apache.cxf</groupId>
  <artifactId>cxf-codegen-plugin</artifactId>
  <executions>
    <execution>
      <id>generate-sources</id>
      <phase>generate-sources</phase>
      <configuration>
        <sourceRoot>src/generated/java</sourceRoot>
        <wsdlOptions>
          <wsdlOption>
            <wsdl>${basedir}/src/main/resources/wsdl/Accounts.wsdl</wsdl>
          </wsdlOption>
        </wsdlOptions>
      </configuration>
      <goals>
        <goal>wsdl2java</goal>
      </goals>
    </execution>
  </executions>
</plugin>

The sourceRoot element tells the plugin to put the generated java classes in a new source directory src/generated/java. This separates the generate classes from classes we'll write ourselves. The wsdl element points to the WSDL file we created earlier.

To run the code generation open a command window and run mvn generate-sources.

Image title

Refresh your IDE workspace and you'll see two new packages. By default, the package names are based on the namespaces in the WSDL. The contents of both packages are described below.

  • com.blog.demo.webservices.accountservice - contains the four domain objects, Account , AccountDetailsRequest, AccountDetailsResponse and EnumAccountStatus. These are the core types we defined in the XSD earlier and will form the main building blocks of the service.
  • com.briansdevblog.accounts - contains the Service Endpoint Interface AccountService. This interface is a Java representation of the service operation defined in the WSDL. AccountService_Service. is a web service client and can be used to invoke the service.

Service Endpoint Interface

The generated Service Endpoint Interface AccountService contains a method for each operation defined in the WSDL. As you'd expect, the method parameter and return type are those defined in the XSD and referenced in the WSDL.

@WebService(targetNamespace = "http://www.briansdevblog.com/Accounts/", name = "AccountService")
@XmlSeeAlso({com.blog.demo.webservices.accountservice.ObjectFactory.class})
@SOAPBinding(parameterStyle = SOAPBinding.ParameterStyle.BARE)
public interface AccountService {

    @WebMethod(operationName = "GetAccountDetails", action = "http://www.briansjavablog.com/Accounts/GetAccountDetails")
    @WebResult(name = "AccountDetailsResponse", targetNamespace = "http://com/blog/demo/webservices/accountservice", partName = "parameters")
    public com.blog.demo.webservices.accountservice.AccountDetailsResponse getAccountDetails(

        @WebParam(partName = "parameters", name = "AccountDetailsRequest", targetNamespace = "http://com/blog/demo/webservices/accountservice")
        com.blog.demo.webservices.accountservice.AccountDetailsRequest parameters
    );
}
  • @WebService - Marks the class as defining a Web Service interface from a WSDL. The namespace should match the namespace defined in the WSDL and the name should match the WSDL Port Type.
  • @XmlSeeAlso - Lets JAXB know what other classes need to be registered with the JAXB context for serialization and deserialization.
  • @SoapBinding - Describes mapping from web service operations to the SOAP protocol.
  • @WebMethod - Maps a service operation to a Java method. The operation name references the operation defined in the WSDL and the target namespace uses the namespace associated with the WSDL operation.
  • @WebResult - Maps a service operation response message to a Java return type. The name refers to the response message name defined in the WSDL. The target namespace uses the namespace associated with the WSDL message and the partName refers to wsdl:part name in the WSDL.
  • @WebParam - Maps a service operation request message to a Java parameter type. The name refers to the request message name defined in the WSDL. The target namespace uses the namespace associated with the WSDL message and the partName refers to wsdl:part name in the WSDL.

Writing the Service Endpoint

Next, we're going to create the service endpoint by implementing the AccountService interface.

@Service
public class AccountServiceEndpoint implements AccountService {

  @Override
  public AccountDetailsResponse getAccountDetails(AccountDetailsRequest parameters) {

    ObjectFactory factory = new ObjectFactory();
    AccountDetailsResponse response = factory.createAccountDetailsResponse();

    Account account = factory.createAccount();
    account.setAccountNumber("12345");
    account.setAccountStatus(EnumAccountStatus.ACTIVE);
    account.setAccountName("Joe Bloggs");
    account.setAccountBalance(3400);

    response.setAccountDetails(account);
    return response;
  }

}

The endpoint implements the getAccountDetails method on the AccountService interface. To keep things simple the method body returns some hard-coded Account data wrapped in a AccountDetailsResponse.

Configuring the Service With Spring

In the past, we would have configured the service endpoint via XML, but now that we're using Spring Boot we can move all our configuration to Java. The XML config was okay, but it's hard to beat configuring Spring with Java. The ApplicationConfig class below contains all the configuration required to run the service. I'll describe each bean below.

@Configuration
public class ApplicationConfig {

  @Bean
  public ServletRegistrationBean<CXFServlet> dispatcherServlet() {
    return new ServletRegistrationBean<CXFServlet>(new CXFServlet(), "/soap-api/*");
  }

  @Bean
  @Primary
  public DispatcherServletPath dispatcherServletPathProvider() {
      return () -> "";
  }

  @Bean(name=Bus.DEFAULT_BUS_ID)
  public SpringBus springBus(LoggingFeature loggingFeature) {

    SpringBus cxfBus = new  SpringBus();
    cxfBus.getFeatures().add(loggingFeature);

    return cxfBus;
  }

  @Bean
  public LoggingFeature loggingFeature() {

    LoggingFeature loggingFeature = new LoggingFeature();
    loggingFeature.setPrettyLogging(true);

    return loggingFeature;
  }

  @Bean
  public Endpoint endpoint(Bus bus, AccountServiceEndpoint accountServiceEndpoint) {

    EndpointImpl endpoint = new EndpointImpl(bus, accountServiceEndpoint);
    endpoint.publish("/service/accounts");

    return endpoint;
  }

}
  • ServletRegistrationBean<CXFServlet> - registers the CXF dispatcher servlet to handle incoming HTTP requests to /soap-api/*. The dispatcher servlet essentially routes requests to an endpoint for processing.
  • SpringBus - is a Spring flavoured CXF Bus. A Bus is a core CXF extension point that allows you to add interceptors to any CXF client or endpoint that uses the bus. In the example above we add the injected LoggingFetaure to enable logging.
  • LoggingFeature - A Feature is something that adds some functionality to a CXF client or server. In this instance the LoggingFeature performs logging of the inbound and outbound SOAP payload. I've enabled pretty logging to make the SOAP messages a little more readable.
  • Endpoint - exposes a HTTP endpoint to process incoming SOAP requests. The publish method tells CXF to publish the endpoint at /service/accounts. This path will be appended to the /soap-api/* pattern used to configure the CXFServlet earlier. This means the endpoint is exposed at CONTEXT_ROOT/soap-api/service/accounts.

Writing an Integration Test

Next, we're going to write an integration test to make sure everything is working as expected. The test will do the following:

  • Use Jetty to stand up and an instance of the endpoint at http://localhost:8080/services/accounts.
  • Create a web service client/SOAP proxy to handle serialization of request and deserialization of response.
  • Create an AccountDetailsRequest, send it to the SOAP endpoint and check the contents of the AccountDetailsResponse
  • Tear down the test endpoint.

Related tutorial: How to Integrate Cucumber for Spring Boot Integration Tests

You'll notice that the test doesn't use ApplicationConfig we defined earlier but instead uses the following test specific config.

@Configuration
@ComponentScan("com.blog.demo.service")
public class TestConfig {

  private static final String SERVICE_URL = "http://localhost:8080/services/accounts";

  @Bean("accountServiceClient")
  public AccountService accountServiceClient() {

    JaxWsProxyFactoryBean jaxWsProxyFactoryBean = new JaxWsProxyFactoryBean();
    jaxWsProxyFactoryBean.setServiceClass(AccountService.class);
    jaxWsProxyFactoryBean.setAddress(SERVICE_URL);    
    return (AccountService) jaxWsProxyFactoryBean.create();
  }

  @Bean(name=Bus.DEFAULT_BUS_ID)
  public SpringBus springBus(LoggingFeature loggingFeature) {

    SpringBus bus = new  SpringBus();
    bus.getFeatures().add(loggingFeature);

    return bus;
  }

  @Bean
  public LoggingFeature loggingFeature() {

    LoggingFeature loggingFeature = new LoggingFeature();
    loggingFeature.setPrettyLogging(true);

    return loggingFeature;
  }

  @Bean
  public Endpoint endpoint(Bus bus, LoggingFeature loggingFeature, AccountServiceEndpoint accountServiceEndpoint) {

    EndpointImpl endpoint = new EndpointImpl(bus, accountServiceEndpoint);
    endpoint.publish(SERVICE_URL);

    return endpoint;
  }

The accountServiceClient method uses the JaxWsProxyFactoryBean to create a web service client for the AccountService Service Endpoint Interface. The client is configured to call the endpoint at http://localhost:8080/services/accounts. In order to stand up a test instance of the endpoint, we also configure a SpringBus, LoggingFeature, and Endpoint similar to the way we did in ApplicationConfig.

The AccountServiceEndpointTest below uses the injected accountServiceClient from TestConfig.

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = { TestConfig.class })
public class AccountServiceEndpointTest {

  @Autowired
  @Qualifier("accountServiceClient")
  private AccountService accountsServiceClient;
  private AccountDetailsRequest accountDetailsRequest;

  @Before
  public void setUp() throws Exception {

    ObjectFactory objectFactory = new ObjectFactory();
    accountDetailsRequest = objectFactory.createAccountDetailsRequest();
    accountDetailsRequest.setAccountNumber("12345");
  }

  @Test
  public void testGetAccountDetails() throws Exception {

    AccountDetailsResponse response = accountsServiceClient.getAccountDetails(accountDetailsRequest);
    assertTrue(response.getAccountDetails()!= null);
    assertTrue(response.getAccountDetails().getAccountNumber().equals("12345"));
    assertTrue(response.getAccountDetails().getAccountName().equals("Joe Bloggs"));
    assertTrue(response.getAccountDetails().getAccountBalance() == 3400);
    assertTrue(response.getAccountDetails().getAccountStatus().equals(EnumAccountStatus.ACTIVE));
  }

}

Running the Test

You can run the integration test in your IDE or fire up the command line and run mvn test. You'll see Jetty start on port 8080 and the SOAP request/response payloads logged when the endpoint is called. Learn how to check which process is using port 8080.

Running as a Stand Alone Service

At this point, we've run the integration test and everything is behaving as expected. The only thing left to do is to fire up the service with Spring Boot. On the command line run mvn spring-boot:run. The service should start on port 8090 as shown below.

If you browse to http://localhost:8090/soap-api you'll see the standard CXF service page with the Account Service listed and a link to the WSDL.

You can now test the service using any standard HTTP client. Below I use Postman to send a POST request to http://localhost:8090/soap-api/service/accounts. The SOAP response is displayed as expected.

Read our related tutorial on how to get an access token from Keycloak with Postman.

Wrapping Up

In this post, we built a SOAP Web Service from scratch using Apache CXF and Spring Boot. We began by defining the data model and WSDL (contract first) and then moved on to implement the service endpoint. We looked at how a CXF endpoint is configured in Spring Boot and also put together a simple integration test. The full source code for this post is available on GitHub.

Spring Framework Web Service Spring Boot Apache CXF SOAP Web Protocols integration test

Opinions expressed by DZone contributors are their own.

Related

  • Consuming SOAP Service With Apache CXF and Spring
  • Spring Boot - How To Use Native SQL Queries | Restful Web Services
  • RESTful Web Services: How To Create a Context Path for Spring Boot Application or Web Service
  • How To Validate HTTP Post Request Body - Restful Web Services With Spring Framework | Spring Boot

Partner Resources

×

Comments
Oops! Something Went Wrong

The likes didn't load as expected. Please refresh the page and try again.

ABOUT US

  • About DZone
  • Support and feedback
  • Community research
  • Sitemap

ADVERTISE

  • Advertise with DZone

CONTRIBUTE ON DZONE

  • Article Submission Guidelines
  • Become a Contributor
  • Core Program
  • Visit the Writers' Zone

LEGAL

  • Terms of Service
  • Privacy Policy

CONTACT US

  • 3343 Perimeter Hill Drive
  • Suite 100
  • Nashville, TN 37211
  • support@dzone.com

Let's be friends:

Likes
There are no likes...yet! 👀
Be the first to like this post!
It looks like you're not logged in.
Sign in to see who liked this post!