Ready to discover a way to generate usable Java classes from WSDL files and check how they are structured? That is precisely where this blog post will get you.

Finishing the last line, you’ll be equipped with all the necessary schema files and the ready-to-use service.

Sounds promising? Only if you know what WSDL files are, so let’s start with defining our key phrase.

One way of communication between applications is SOAP protocol based on XML documents. WSDL files come in handy when describing the application's web services using SOAP without writing documentation. You can use them to generate the code ready for API testing and the object structure that can be mapped from incoming responses and is necessary for creating requests. 

This scenario comes straight from our recent project — we’ve got WSDL files from one of the applications we need to reach as a part of the instruction on communicating with them.

The description of each package in the service consisted of two files.

  1. The .xsd file with a defined target namespace, names of data classes in the given package, and types and names of their fields, which describes schema.

  2. The .wsdl file describing services, such as their name and all the methods with parameters and types returned.

Mind that it’s also possible to describe schema and service in one WSDL file instead of two separate ones.

HOW TO GENERATE THE CLASSES 

Start with adding both WSDL files to your project. 

No framework is necessary, but let’s assume it’s a Spring project. 

The default directory for the files is src/main/resources. Then, you need the library to generate classes. 

In our project, we decided to use cxf-codegen-plugin. It is possible to use this plugin with Gradle, but we chose Maven, so I will use it as an example. 

The basic Maven dependency, together with the execution configuration, is quite simple:

<plugin>
  <groupId>org.apache.cxf</groupId>
  <artifactId>cxf-codegen-plugin</artifactId>
  <version>3.5.2</version>
  <executions>
     <execution>
         <id>generate-sources</id>
         <phase>generate-sources</phase>
         <configuration>
         <sourceRoot>$/generated-sources/cxf</sourceRoot>
           <wsdlOptions>
                   <wsdlOption>
                       <wsdl>$/src/main/resources/schemas/Order.wsdl</wsdl>
                   </wsdlOption>
               </wsdlOptions>
         </configuration>
         <goals>
             <goal>wsdl2java</goal>
         </goals>
     </execution>
  </executions>
</plugin>

And that’s it. Now, it’s possible to generate the classes only with this additional dependency in the project.

You can also customise how the classes are generated using the <wsdlOptions> tag in the configuration part.

What exactly can be customised?

  • Name of the generated service:

<wsdlOption>
   <wsdl>$/src/main/resources/wsdl/myService.wsdl</wsdl>
   <serviceName>MyWSDLService</serviceName>
</wsdlOption>
  • Name of the created package:

<configuration>
   <sourceRoot>$/wsdl/service</sourceRoot>
</configuration>

By default, files are generated in ${project.build.directory}/generated-sources/cxf/ticket. With the given configuration, it would be ${project.build.directory}/wsdl/service/ticket package.

  • Single WSDL files we don’t want to generate classes from:

<wsdlRoot>$/src/main/resources/wsdl</wsdlRoot>
<excludes>
   <exclude>*Service.wsdl</exclude>
</excludes>

The final step is running the mvn clean install command. When you get the build success, your classes are ready.

LET’S SEE HOW IT WORKS

Let’s generate classes from Ticket.wsdl and Ticket.xsd files to see it on a specific example. We can observe that they describe the TicketService and all the necessary schema classes.
Ticket.wsdl file:

<wsdl:definitions targetNamespace="http://ticket" xmlns:ns="http://ticket" xmlns:wsdl="http://schemas.xmlsoap.org/wsdl/"
             xmlns:http="http://schemas.xmlsoap.org/wsdl/http/" xmlns:xs="http://www.w3.org/2001/XMLSchema">
 
  <wsdl:documentation>Ticket Service</wsdl:documentation>
  <wsdl:types>
     <xs:schema targetNamespace="http://ticket">
        <xs:include schemaLocation="Ticket.xsd"/>
     </xs:schema>
  </wsdl:types>
  <wsdl:message name="Ticket">
     <wsdl:part name="parameters" element="ns:Ticket">
     </wsdl:part>
  </wsdl:message>
  <wsdl:message name="TicketOrder">
     <wsdl:part name="customerId" type="xs:string">
     </wsdl:part>
  </wsdl:message>
 
  <wsdl:portType name="TicketInterface">
     <wsdl:operation name="GetTicket">
        <wsdl:input message="ns:TicketOrder"/>
        <wsdl:output message="ns:Ticket"/>
     </wsdl:operation>
  </wsdl:portType>
 
  <wsdl:binding name="TicketServiceHttpBinding" type="ns:TicketInterface">
     <http:binding verb="GET" />
     <wsdl:operation name="GetTicket">
        <http:operation location="GetTicket" />
     </wsdl:operation>
  </wsdl:binding>
  <wsdl:service name="TicketService">
     <wsdl:port name="TicketHttpEndpoint" binding="ns:TicketServiceHttpBinding">
        <http:address location="http://localhost:8080/ticket/"/>
     </wsdl:port>
  </wsdl:service>
 
</wsdl:definitions>

Ticket.xsd file:

<xs:schema targetNamespace="http://ticket"
 xmlns:ax21="http://ticket" xmlns:xs="http://www.w3.org/2001/XMLSchema">
 <xs:import namespace="http://ticket"/>
 
 <xs:element name="Ticket">
   <xs:complexType>
     <xs:sequence>
       <xs:element name="customerId" type="xs:string" />
       <xs:element name="ticketType" type="ax21:TicketType" />
     </xs:sequence>
   </xs:complexType>
 </xs:element>
 
 <xs:simpleType name="TicketType">
   <xs:restriction base="xs:string">
     <xs:enumeration value="single">
     </xs:enumeration>
     <xs:enumeration value="child">
     </xs:enumeration>
     <xs:enumeration value="family">
     </xs:enumeration>
   </xs:restriction>
 </xs:simpleType>
 
</xs:schema>

Below there’s a picture showing what the generated structure looks like:

According to the Ticket.xsd, two files were generated — a ticket class containing two fields with getters and setters and an enum class TicketType with defined values. 

In the WSDL definition, it’s also possible to validate field values with @XmlElement annotation, e.g. making them required with the ‘required = true’  parameter, like by customerId and ticketType fields. This parameter is the default, and to change it, you would have to add a ‘minOccurs=”0”’ parameter in the <xs:element> tags defining these fields.

@XmlType(name = "TicketType")
@XmlEnum
public enum TicketType {
 
   @XmlEnumValue("single")
   SINGLE("single"),
   @XmlEnumValue("child")
   CHILD("child"),
   @XmlEnumValue("family")
   FAMILY("family");
   private final String value;
 
   TicketType(String v) {
       value = v;
   }
 
   public String value() {
       return value;
   }
 
   public static TicketType fromValue(String v) {
       for (TicketType c: TicketType.values()) {
           if (c.value.equals(v)) {
               return c;
           }
       }
       throw new IllegalArgumentException(v);
   }
}

@XmlAccessorType(XmlAccessType.FIELD)
@XmlType(name = "", propOrder = {
       "customerId",
       "ticketType"
})
@XmlRootElement(name = "Ticket")
public class Ticket {

   @XmlElement(required = true)
   protected String customerId;
   @XmlElement(required = true)
   @XmlSchemaType(name = "string")
   protected TicketType ticketType;

   public String getCustomerId() {
       return customerId;
   }

   public void setCustomerId(String value) {
       this.customerId = value;
   }

   public TicketType getTicketType() {
       return ticketType;
   }

   public void setTicketType(TicketType value) {
       this.ticketType = value;
   }
}

Based on Ticket.wsdl, you obtain an interface TicketInterface with one method. As specified, it gets a String parameter and returns a Ticket class instance.

@WebService(targetNamespace = "http://ticket", name = "TicketInterface")
@XmlSeeAlso()
@SOAPBinding(parameterStyle = SOAPBinding.ParameterStyle.BARE)
public interface TicketInterface {

   @WebMethod(operationName = "GetTicket")
   @WebResult(name = "Ticket", targetNamespace = "http://ticket", partName = "parameters")
   public Ticket getTicket(

           @WebParam(partName = "customerId", name = "customerId", targetNamespace = "")
           java.lang.String customerId
   );
}

The TicketService class contains the method returning TicketInterface, which allows us to use the getTicket() method and test the connection with the external API:

@WebEndpoint(name = "TicketHttpEndpoint")
public TicketInterface getTicketHttpEndpoint() {
   return super.getPort(TicketHttpEndpoint, TicketInterface.class);
}

In the ticket package, there’s also the ObjectFactory class, with methods you can use for creating new instances of all the other generated types.

HOW TO USE GENERATED CLASSES

When the classes are ready, you can use them like any other class. If the project uses Spring, it may be useful to register a bean in a configuration class implementing BeanDefinitionRegistryPostProcessor, as below:

@Configuration
public class BeanLoader implements BeanDefinitionRegistryPostProcessor {

   @Override
   public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException 
       
   

   @Override
   public void postProcessBeanFactory(ConfigurableListableBeanFactory configurableListableBeanFactory) throws BeansException {

   }

import ticket.ObjectFactory;

   private void registerOrderFactory(BeanDefinitionRegistry registry) throws BeansException {
       BeanDefinitionBuilder builder = BeanDefinitionBuilder
               .genericBeanDefinition(ObjectFactory.class);
       registry.registerBeanDefinition("TicketFactory", builder.getBeanDefinition());
   }
}

As you understand by now, using WSDL files allows you to test the connection quickly and save time you would otherwise use to prepare the model of the received response.

Why not give it a try?