Play is a web application framework for Java and Scala. SOAP, originally defined as Simple Object Access Protocol, is a specification for exchanging structured information (i.e. sending messages) over computer networks. SOAP is widely supported, a notable implementation for Java (and Scala) is the Apache CXF library.

In this tutorial, we’re going to create a Play application which also acts as a SOAP service and serves requests using Apache CXF so that our Hello World service “just works”.

Update: the following tutorial uses Play 2.2.x code and samples. Ported versions exist for Play 2.4.x, 2.3.x and 2.1.x in the play-2.4.x, play-2.3.x and play-2.1.x branches of the repository.

Update: for Play 2.4, use the library version 1.2.1 instead of 1.2.0. For other versions, 1.2.0 is the most up-to-date version available.

Play Framework makes it easy to build web applications with Java & Scala. It has a lightweight, stateless, web-friendly architecture and enables some really neat features, like high scalability or the “Hit refresh workflow”.

SOAP, originally defined as Simple Object Access Protocol, is a specification for exchanging structured information (i.e. sending messages) over computer networks. It relies on other transport protocols, like HTTP (Hypertext Transfer Protocol) or SMTP (Simple Mail Transfer Protocol) for message transmission. SOAP is widely supported, its two notable implementations for Java (and Scala) are the Apache Axis2 and Apache CXF libraries.

In this tutorial, we’re going to create a Play application which also acts as a SOAP service and serves requests using Apache CXF so that our JAX-WS annotated Hello World service “just works”:


// HelloWorld.java
package services.hello;

import javax.jws.WebService;

@WebService
public interface HelloWorld {
    String sayHi(String text);
}
// HelloWorldImpl.java
package services.hello;

import javax.jws.WebService;

@WebService(endpointInterface = "services.hello.HelloWorld")
public class HelloWorldImpl implements HelloWorld {

    @Override
    public String sayHi(String text) {
        return "Hello " + text;
    }
}
HelloWorld.sayHi(" world!") = "Hello world!" using SOAP

Example SOAP request and response for HelloWorld.sayHi

Getting it done

For those in a hurry, here’s what you have to do:

  1. Create a Hello World application, named play-cxf-hello, using the following command:
    play new play-cxf-hello

    Alternatively, you can use another, already existing Play application. Note that the play-cxf repository contains working sample applications in the samples directory.
    The play command is part of Play Framework. If you didn’t set it up yet, you can do so by following its installation guide.
    Update: as of Play 2.3, activator replaces play as the command line tool of the framework. It’s backward compatible, so you can just use activator instead of play everywhere in the tutorial.

  2. Add the following dependencies to your Hello World application:
    libraryDependencies += "org.springframework" % "spring-context" % "4.2.0.RELEASE"
    
    libraryDependencies += "eu.imind.play" %% "play-cxf_play22" % "1.2.0"
    
    libraryDependencies += "org.apache.cxf" % "cxf-rt-bindings-soap" % "3.1.2"
    
    libraryDependencies += "org.apache.cxf" % "cxf-rt-frontend-jaxws" % "3.1.2"

    Note that the second line references a Play Framework module which is dependent on the framework version you use. For Play 2.2, the dependency name is play-cxf_play22. For Play 2.4, 2.3 and 2.1, use play-cxf_play24, play-cxf_play23 and play-cxf_play21 respectively.
    Note: for Play 2.4, you should use play-cxf_play24 version 1.2.1 instead of 1.2.0.
    The above snippet uses the build.sbt syntax. The whole build.sbt file should look similar to the following:

    name := "play-cxf-hello"
    
    version := "1.0-SNAPSHOT"
    
    libraryDependencies += "org.springframework" % "spring-context" % "4.2.0.RELEASE"
    
    libraryDependencies += "eu.imind.play" %% "play-cxf_play22" % "1.2.0"
    
    libraryDependencies += "org.apache.cxf" % "cxf-rt-bindings-soap" % "3.1.2"
    
    libraryDependencies += "org.apache.cxf" % "cxf-rt-frontend-jaxws" % "3.1.2"
    
    play.Project.playScalaSettings

    There is a detailed documentation about managing Play Framework dependencies.

  3. Apache CXF uses Spring for its configuration. First, create a Spring configuration in our Hello World application’s conf/applicationContext.xmlfile:
    <!-- applicationContext.xml -->
    <beans xmlns="http://www.springframework.org/schema/beans"
           xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
           xmlns:jaxws="http://cxf.apache.org/jaxws"
           xsi:schemaLocation="
     http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.0.xsd
     http://cxf.apache.org/jaxws http://cxf.apache.org/schemas/jaxws.xsd
     ">
    
        <!-- Import Apache CXF configuration and Play! transport plugin. -->
        <import resource="classpath:cxf.xml"/>
    
    </beans>

    Then create a Global.scala file in the app directory, with the following contents:

    // Global.scala
    import org.springframework.context.support.ClassPathXmlApplicationContext
    import play.api.{Application, GlobalSettings}
    
    object Global extends GlobalSettings {
    
      val ctx = new ClassPathXmlApplicationContext("applicationContext.xml")
    
      override def onStart(app: Application) {
        super.onStart(app)
        ctx.start()
      }
    
      override def onStop(app: Application) {
        ctx.stop()
        super.onStop(app)
      }
    }

    This will initialize the Spring framework when our application starts up. More about the Global object in the Play Framework documentation.

  4. Add the following lines to the Hello World project’s conf/routesfile:
    # Apache CXF controller
    GET     /service/*path              org.apache.cxf.transport.play.CxfController.handle(path)
    POST    /service/*path              org.apache.cxf.transport.play.CxfController.handle(path)

    More about the routes file in its documentation.

  5. At this point, the application is ready to serve SOAP-based services using JAX-WS. In order to create the example HelloWorld service, place the already listed HelloWorld.java and HelloWorldImpl.java in the app/services/hello/ directory. After that, configure the service by modifying the conf/applicationContext.xmlfile so that it contains the following:
    <!-- applicationContext.xml -->
    <beans xmlns="http://www.springframework.org/schema/beans"
           xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
           xmlns:jaxws="http://cxf.apache.org/jaxws"
           xsi:schemaLocation="
     http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.0.xsd
     http://cxf.apache.org/jaxws http://cxf.apache.org/schemas/jaxws.xsd
     ">
    
        <!-- Import Apache CXF configuration and Play! transport plugin. -->
        <import resource="classpath:cxf.xml"/>
    
        <!-- Define Hello World endpoint. It will be available at http://localhost:9000/service/hello -->
        <jaxws:endpoint name="helloWorld"
                        transportId="http://schemas.xmlsoap.org/soap/http"
                        address="/service/hello"
                        implementor="services.hello.HelloWorldImpl"/>
    
    </beans>

    If you want to use another URL prefix instead of /service/, take care to modify the routes file as well so that the path given there matches the one in the Spring configuration.
    More about the JAX-WS Spring configuration in the Apache CXF docs.

  6. That is all! Run the application by issuing the following commands from the console:
    cd play-cxf-hello
    play run

    You should be able to access the SOAP service’s WSDL descriptor at http://localhost:9000/service/hello?wsdl and use the service with the http://localhost:9000/service/hello endpoint. You can test it using SoapUI.
    Try to add another method to the service! You won’t need to recompile or redeploy anything, just download the WSDL descriptor again. The new method should show up instantly.

Frequently Asked Questions

Why so complicated?

The Play Framework has built-in support for creating RESTful web services, but no support for SOAP at all. In order to use SOAP services (and in particular, Apache CXF) with Play Framework, we needed a small Play module which acts as a compatibility layer between the two systems. You may wonder why it’s such a difficult task to set up SOAP with Play when it works effortlessly with just about any other Java or Scala web framework. The answer is in Play Framework’s decision to abandon the Java Servlet API, which serves to be the connecting point between the HTTP transport plugin of Apache CXF and the application container. The solution is to create a custom transport plugin to connect Apache CXF to Play’s Controller API. This transport plugin is what’s in our Play module.

Furthermore, Apache CXF supports a number of configuration methods, a custom CXFServlet servlet being one of them, using Spring another. The former isn’t supported in Play due to the lack of the Servlet API, so we used the latter.

How to enable WSDL-based validation?

Add the following to your endpoint configuration:

    <jaxws:endpoint ... >
        <jaxws:properties>
            <entry key="schema-validation-enabled" value="true"/>
        </jaxws:properties>
    </jaxws:endpoint>

Note that validation is enabled for your service’s generated responses, too, not just for incoming requests. Currently, if the response doesn’t pass validation, then the service will return nothing and the validation exception gets swallowed, so it can be tricky to find the cause. If you encounter a similar issue, remember to disable validation as a first step to solve it.

I have … issue with the generated WSDL!

Don’t use the Java-first approach for building a service. It’s fine for Hello World examples, but for anything more, create your own WSDL file, then generate the service code from that.
You can then configure the JAX-WS endpoint to use an external WSDL file for the ?wsdl URL and for validation with the wsdlLocation property.

How to have host-specific endpoint addresses?

Have a look at the MultiEnv sample.

Can I use play-cxf as a SOAP client?

No. This plugin is for creating SOAP services. It’s not needed for calling other SOAP services. All Java or Scala SOAP clients should work out-of-the-box with Play Framework.

Error: type CxfController is not a member of package org.apache.cxf.transport.play

You’re probably using Play 2.4 with its new dependency injection. Please switch the play-cxf_play24 version to 1.2.1 in your project to fix the issue.

What’s next?

For more information about developing a JAX-WS web service (that is: how to develop a slightly more complex HelloWorldImpl class?), refer to the Apache CXF docs about the topic.

In order to learn more about the Play Framework, you can visit its online documentation.

If you have any comments, you can find me at laszlo.kustra@imind.eu  You can also follow us for updates on twitter, or facebook.