Calling web service APIs using Java, Spring, JAX-WS and Maven

This is a recipe for calling web service SOAP/WSDL-based APIs from a Java/Spring application that is built with Maven. As example, I’ll use the Campaign Monitor Web Service API.

Generating client classes

First step is to generate client classes from the WSDL file. This task can be left to the jaxws-maven-plugin. It suffices to copy the wsdl file to a dedicated web service definition folder and configure the plug-in to use this folder:

<build>
    <plugins>
        <plugin>
            <groupId>org.codehaus.mojo</groupId>
            <artifactId>jaxws-maven-plugin</artifactId>
            <executions>
                <execution>
                    <goals>
                        <goal>wsimport</goal>
                    </goals>
                </execution>
            </executions>
            <configuration>
                <wsdlDirectory>${basedir}/src/main/java/com/example/webservices</wsdlDirectory>
                <bindingDirectory>${basedir}/src/main/java/com/example/webservices</bindingDirectory>
            </configuration>
        </plugin>
    </plugins>
</build>

This generates the client classes automatically under target/jaxws/wsimport/java when the project is built. You can trigger the generation manually using:

mvn generate-sources

Customizing the client classes

Such a generation might yields errors like naming conflicts that require customization. Or you might want to customize package and class names. This is possible using a file wsdl-customization.xml in the same folder as the .wsdl:

<?xml version="1.0" encoding="utf-8"?>
<jaxws:bindings
    wsdlLocation="campaignmonitor.wsdl"
    xmlns:jaxws="http://java.sun.com/xml/ns/jaxws"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:xs="http://www.w3.org/2001/XMLSchema"
    xmlns:wsdl="http://schemas.xmlsoap.org/wsdl/"
    xmlns:jaxb="http://java.sun.com/xml/ns/jaxb"
    xsi:schemaLocation="
        http://java.sun.com/xml/ns/jaxws http://java.sun.com/xml/ns/jaxws/wsdl_customizationschema_2_0.xsd
        http://java.sun.com/xml/ns/jaxb http://java.sun.com/xml/ns/jaxb/bindingschema_2_0.xsd">

    <jaxws:package name="com.campaignmonitor.services" />

    <jaxws:bindings node="//wsdl:portType[@name='apiSoap']">
        <jaxws:class name="CampaignMonitorService" />
    </jaxws:bindings>

     <jaxws:bindings node="//xs:schema[@targetNamespace='http://api.createsend.com/api/']">
        <jaxws:bindings node="xs:complexType[@name='SubscriberUnsubscribe']">
            <jaxb:class name="SubscriberUnsubscribeType"/>
        </jaxws:bindings>
        <jaxb:schemaBindings>
            <jaxb:package name="com.campaignmonitor.types"/>
        </jaxb:schemaBindings>
    </jaxws:bindings>

</jaxws:bindings>

Configuring the service as Spring Bean

The Spring framework provides a factory class JaxWsPortProxyFactoryBean to configure an instance of the generated interface CampaignMonitorService:

<bean id="campaignMonitorService" class="org.springframework.remoting.jaxws.JaxWsPortProxyFactoryBean">
    <property name="serviceInterface" value="com.campaignmonitor.services.CampaignMonitorService" />
    <property name="wsdlDocumentUrl" value="classpath:com/example/webservices/campaignmonitor.wsdl" />
    <property name="namespaceUri" value="http://api.createsend.com/api/" />
    <property name="serviceName" value="api" />
    <property name="endpointAddress" value="https://api.createsend.com/api/api.asmx" />
</bean>

Thanks to Gunnar Morling for giving an example in his blog: Writing and testing JAX-WS clients using the Spring framework

Service wrappers

Often such APIs might be rather rudimentary and it might make sense to wrap them in a separate service. For the Campaign Monitor API I created a NewsletterService which handles subscribing to one subscriber list and can easily be extended with other operations:

@Service
public class NewsletterService {

    private static final Logger log = LoggerFactory.getLogger(NewsletterService.class);

    @Autowired
    private CampaignMonitorService campaignMonitorService;
    private String apiKey;
    private String listId;

    public void subscribeNewsletter(String email) {
        SubscriberAddAndResubscribe request = new SubscriberAddAndResubscribe();
        request.setApiKey(apiKey);
        request.setListID(listId);
        request.setName("");
        request.setEmail(email);
        Result r = campaignMonitorService.addAndResubscribe(request).getSubscriberAddAndResubscribeResult();

        if (r.getCode() != 0) {
            throw new RuntimeException(r.getCode() + " " + r.getMessage());
        }
    }

    public void setApiKey(String apiKey) {
        this.apiKey = apiKey;
    }

    public void setListId(String listId) {
        this.listId = listId;
    }

}

This is configured as a Spring bean and is used by other services that need to talk to the Campaign Monitor services:

<bean id="newsletterService" class="de.ralfebert.core.company.service.NewsletterService">
    <property name="apiKey" value="${campaignMonitor.apiKey}"/>
    <property name="listId" value="${campaignMonitor.listId}"/>
</bean>

About the author

Ralf Ebert Ralf Ebert is an independent software developer and trainer for Mac OS X and iOS. He makes the Page Layers, TakeOff and Straight ahead apps and conducts iOS trainings and Git trainings in Germany.