How to build Soap Web Services with Apache CXF and Spring Boot
The popularity of Restful Services is rising that does not mean we have forgotten the Soap Web Services. In this article, we will see how to build Soap web services with Apache CXF and Spring Boot.
Softwares used
- Spring Boot 1.5.10.RELEASE
- cxf-spring-boot-starter-jaxws (3.1.12)
- Java 8
- Maven
- Eclipse
Apache CXF is a popularly used services framework and its available as open source. Apache community has now added support for Spring Boot starter dependency so you don’t have to add each capability separately.
Maven Dependencies
1 2 3 4 5 6 7 8 9 |
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.apache.cxf</groupId> <artifactId>cxf-spring-boot-starter-jaxws</artifactId> <version>3.1.12</version> </dependency> |
This covers all the dependencies you need for creating your web service. But if you need to add any feature e.g. Request Logging, its available out of the box and you need to add that dependency separately.
1 2 3 4 5 |
<dependency> <groupId>org.apache.cxf</groupId> <artifactId>cxf-rt-features-logging</artifactId> <version>3.1.12</version> </dependency> |
Apache CXF Service Configuration
The first thing we have to do is configure CXFServlet and SpringBus. These two provides integration point with Spring.
1 2 3 4 5 6 7 8 9 10 |
@Bean public ServletRegistrationBean dispatcherServlet() { return new ServletRegistrationBean(new CXFServlet(), "/services/*"); } @Bean(name=Bus.DEFAULT_BUS_ID) public SpringBus springBus() { SpringBus springBus = new SpringBus(); return springBus; } |
With above configuration, all the service endpoints will be available at URL /services/*
Service Definition
We will see service definition using standard XML as well as using Java configuration.
XML Configuration
1 2 3 4 5 6 7 8 |
<jaxws:endpoint id="GreetingService" implementor="#greetingServiceImpl" address="/GreetingService"> <jaxws:features> <bean class="org.apache.cxf.ext.logging.LoggingFeature"></bean> </jaxws:features> </jaxws:endpoint> |
The above configuration will expose soap endpoint at given address. Also, notice that We have also mentioned logging feature to be enabled. This feature will make sure that soap request along with other request related parameters is logged. This data is logged at two points, one when a request is first received by framework and two, just before a response is sent out.
Let’s see corresponding java classes:
Greeting Service Interface
1 2 3 4 5 6 7 8 9 10 11 |
@WebService(serviceName = "GreetingService") public interface GreetingService { @WebMethod() @WebResult(name = "Greeting") public Greeting sayHello(@WebParam(name = "GreetingsRequest") String name); @WebMethod() @WebResult(name = "Greeting") public Greeting sayBye(@WebParam(name = "GreetingsRequest") String name); } |
Greeting Service Implementation
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
@Service public class GreetingServiceImpl implements GreetingService { @Override public Greeting sayHello(String name) { Greeting greeting = new Greeting(); greeting.setMessage("Hello " + name + "!!!"); greeting.setDate(new Date()); return greeting; } @Override public Greeting sayBye(String name) { Greeting greeting = new Greeting(); greeting.setMessage("Bye " + name + "!!!"); greeting.setDate(new Date()); return greeting; } } |
Java Configuration
The Java configuration of an endpoint is pretty straightforward. Just define the bean as below:
1 2 3 4 5 6 7 |
@Bean public Endpoint endpoint() { EndpointImpl endpoint = new EndpointImpl(springBus(), new InfoServiceImpl()); endpoint.getFeatures().add(new LoggingFeature()); endpoint.publish("/InfoService"); return endpoint; } |
This bean will expose a new endpoint at /InfoService.
InfoService
1 2 3 4 5 6 |
@WebService(serviceName = "InfoService") public interface InfoService { @WebMethod() @WebResult(name = "Greeting") public Greeting sayHowAreYou(@WebParam(name = "GreetingsRequest") String name); } |
InfoService Implementation
1 2 3 4 5 6 7 8 9 10 11 |
@Service public class InfoServiceImpl implements InfoService { @Override public Greeting sayHowAreYou(String name) { Greeting greeting = new Greeting(); greeting.setMessage("How are you " + name + "!!!"); greeting.setDate(new Date()); return greeting; } } |
The Model
As you noticed we are sending an object as response. The model for that is as:
1 2 3 4 5 6 7 8 9 |
@XmlAccessorType(XmlAccessType.FIELD) @XmlRootElement(name="Greeting") public class Greeting { private String message; private Date date; //Getters //Setters } |
Running the service
To make sure that Spring takes note of our XML web services definitions, we need to import it into the main application. The same you can see below:
1 2 3 4 5 6 7 |
@SpringBootApplication @ImportResource({ "classpath:webservice-definition-beans.xml" }) public class Application { public static void main(String[] args) { SpringApplication.run(Application.class, args); } } |
Now go ahead and run your application. It will start at default port 8080. Once started hit the URL: http://localhost:8080/services/ You will see a screen like:
You can even load the WSDL by clicking on the links given in above page.
We can even test this with SoapUI tool
Adding Request Interceptors
Sometimes simply logging the incoming request is not enough. You might have to do some additional processing on the payload. For that Apache CXF has In and Out interceptors at different phases of a request. For illustrations, we will simply capture the incoming request.
Define In Interceptor
1 2 3 4 5 6 7 8 9 10 11 12 13 |
@InInterceptors public class AppInboundInterceptor extends LoggingInInterceptor { @Override public void handleMessage(Message message) throws Fault { processPayLoad(message); super.handleMessage(message); } private void processPayLoad(Message message) { System.out.println("*** PROCESSING PAYLOAD AT IN-INTERCEPTOR **"); } } |
Define Out Interceptor
1 2 3 4 5 6 7 8 9 10 11 12 13 |
@OutInterceptors public class AppOutboundInterceptor extends LoggingOutInterceptor { @Override public void handleMessage(Message message) throws Fault { processPayLoad(message); super.handleMessage(message); } private void processPayLoad(Message message) { System.out.println("*** PROCESSING PAYLOAD AT OUT-INTERCEPTOR **"); } } |
Now how do we attach these to our message? Well, these you can do at two places. one is at Bus level which will be applicable for all the endpoints managed by that Bus. The second option is to attach this interceptor to a particular endpoint.
1 2 3 4 5 6 7 |
@Bean(name=Bus.DEFAULT_BUS_ID) public SpringBus springBus() { SpringBus springBus = new SpringBus(); springBus.getInInterceptors().add(new AppInboundInterceptor()); springBus.getOutInterceptors().add(new AppOutboundInterceptor()); return springBus; } |
That’s it!! Your custom interceptors are ready to take on any message that comes your way.
Conclusion
In this article, we have seen how easy it is to configure and run soap web service using Apache CXF and Spring Boot.
You can download complete source code from our GitHub repository.
Download Code
I’m trying to follow the steps you show here but also adding an asynchronous implementation of the sayBye method @Override
public Future sayByeAsync(String name, AsyncHandler asyncHandler).
I can’t get that to work though, getting an exception when invoking the async method that complain about the return type
Part {http://service.opencodez.com/}parameters should be of type com.opencodez.service.jaxws_asm.SayByeResponse, not com.opencodez.model.Greeting
Any idea how to do an async soap service with cxf and spring boot?
Hi Ola,
Never came across async web service. May be I will try to do that in this example and update