Java 6 Web Services

Posted on May 4, 2009


Overview

As of version 6, Java Standard Edition (“Mustang”) now offers web service capabilities out of the box in much the same way as RMI. Mustang includes:

Background

I recently used Java SE 6 to expose an existing stand-alone pricing application as a web service for use throughout an investment bank. For various reasons, the application was not running as a JEE application, nor was it running within any other container. It was simply exposed as an RMI service using Java SE.

Mustang allowed me to turn the existing application into a web service by annotating existing code with JAX-WS annotations. I then published the service via the Endpoint API. Whilst this was very easy, I did encounter a few sticky points which I discuss in the Additional Notes section below.

Links

I provide here some links I collated when implementing this service. Hopefully these will be useful to anyone developing a web service using Java SE 6.

Additional Notes

A couple of additional observations from my experience implementing a web service using Mustang.

Programming to Interfaces

JAXB doesn’t like interfaces. If you need to expose an object using its interface you’ll need to tell JAXB where to find an implementation of your interface when deserializing.

An example of this problem is when you specify that your web service method (operation) accepts implementations of a given interface, say Loan (interface). You must tell JAXB what implementation of Loan to deserialize to.

// LoanManagerWebService.java
@WebService
public class LoanManagerWebService {

   @WebMethod
   public Price priceLoan(Loan loan) {
      // Implementation
   }
}

The easiest way to do this is to annotate the Loan interface with the following annotation:

// Loan.java
@XmlJavaTypeAdapter(LoanAdapter.class)
public interface Loan {
   // Definition of a Loan
}

…and create a LoanAdapter:

// LoanAdapter.java
public class LoanAdapter extends XmlAdapter<Loan,LoanBean> {

   LoanBean unmarshal(Loan v) {
      return (LoanBean) v;
   }

   Loan marshal(LoanBean v) {
      return v;
   }
}

…which adapts Loan XML to a specific Loan implementation, here a LoanBean:

// LoanBean.java
public class LoanBean implements Loan {
   // Implementation of a Loan
}

This section of the unofficial JAXB guide will help enormously with this and related issues: http://jaxb.dev.java.net/guide/Mapping_interfaces.html. Additionally, this tutorial is also very helpful: Using JAXB 2.0’s XmlJavaTypeAdapter.

Lists versus Arrays

Everyone (or at least most people) know this already. I’ll restate it since it caught me out… Web services and lists (or any sort of language-specific collection) don’t mix well. Web services only like language-agnostic arrays.

How did I manage to make such a school-boy error? Well, I built a solution anticipating usage only via Java and exposed it only as an RMI service. When the need to access it from .NET later emerged (much later), I had the pleasure of rewriting large portions of my input and output object graphs (the public API) to use arrays rather than Lists.

I used Lists in the first place because they are more dynamic and easier to work with when building the object graph. Should I have better forward planned, or would doing so have been an example of over-engineering? I’ll certainly think twice about it next time.

This is clearly something to bear in mind when you have a need to expose an existing application as a web service – JAX-WS and JAXB help enormously, but good (or at least, appropriate) design is still key.

Java Beans

JAXB offers ease-of-use at a cost of conformity to the Java Bean standard. Any object that must be serialized by JAXB (i.e. any object in your public object graph sent to or from the web service operation) must have a public no argument constructor and provide public getters and setters for the properties you wish to get or set from the web service client.

Exceptions

Java exceptions don’t transport well across web service boundaries. This is again a pretty obvious point when you stop and think about it, but it is an issue that only became apparent after I built a C# .NET client to the web service. Throwing a well-defined exception, such as LoanPricingException or InvalidLoanIdentifierException, only results in a general WebException being received by the web service client. This WebException does not contain the well-chosen human-readable message you may have put into an exception either. Nor is the server-side exception accessible as part of or the root of the WebException thrown by the client-side proxy. Basically, all that is known is that something went wrong.

I’m not 100% sure I didn’t miss a trick, but I’m pretty confident I implemented the web service as instructed in all the tutorials. Time being a crucial factor for my client and on consulting fellow colleagues who had encountered similar problems previously, I opted for the path of least resistance. I created a result object that contained any non-catastrophic business errors that occurred on the server-side and/or the actual result of the web service operation, as a payload within the result object. Any business exceptions that occurred were caught and added to the result object. I still added a throws WebServiceException to the web service method for catastrophic web-service-related (i.e. infrastructure) exceptions.

// WebServiceError.java
public class WebServiceError {

   private String message;

   public String getMessage() {
      return message;
   }

   public void setMessage(String message) {
      this.message = message;
   }
}
// WebServiceResult.java
public abstract class WebServiceResult {

   private WebServiceError[] errors;

   public WebServiceError[] getErrors() {
      return errors;
   }

   public void setErrors(WebServiceError[] errors) {
      this.errors = errors;
   }
}
// WebServicePriceResult.java
public class WebServicePriceResult extends WebServiceResult {

   private Price price;

   public Price getPrice() {
      return price;
   }

   public void setPrice(Price price) {
      this.price = price;
   }
}
// LoanManagerWebService.java
@WebService
public class LoanManagerWebService {

   private LoanManager loanManager = new LoanManager();

   @WebMethod
   public WebServicePriceResult priceLoan(Loan loan) throws WebServiceException {

      WebServicePriceResult result = new WebServicePriceResult();      
      List<WebServiceError> errors = new ArrayList<WebServiceError>();

      try {
         Price price = loanManager.priceLoan(loan);
         result.setPrice(price);
      } catch (Exception e) {
          WebServiceError error = new WebServiceError();
          error.setMessage(e.getMessage());
          errors.add(error);
      }

      if (!errors.isEmpty()) {
         result.setErrors(errors.toArray(new WebServiceError[0]));
      }

      return result;
   }
}

Like I said, perhaps I wasn’t using the web service Fault correctly (it certainly looked OK in the WSDL, etc.), but it does make sense to me that something language-specific, such as the make-up of a complex Java Exception object, won’t be common across platforms. Therefore, the sensible and logical design idioms to apply are composition and encapsulation – encapsulating the errors into a complex object that can be transported and that the service doesn’t rely on a language-specific Exception control-flow.

This is an area that certainly interests me and one which I need to look into more… I don’t like things getting the better of me like this.

Posted in: Development