Using SOAP Faults and Exceptions in Java JAX-WS Web Services
Pick up a copy of Java SOA Cookbook by Eben Hewitt for more stuff like this.
This article shows you how to avoid this error: javax.xml.ws.soap.SOAPFaultException: java.lang.NoSuchMethodException
This article shows you how to create proper throws clauses in your Java web services, and how to write JUnit 4.4 unit tests to handle the exceptions that you get, and mapping semantics with Java exceptions and SOAP Faults. It also shows you how to use the @WebFault annotation.
If you see a stack trace like this in your Java web service client, the answer is below:
javax.xml.ws.soap.SOAPFaultException: java.lang.NoSuchMethodException: com.example.MyExceptionBean.setMessage(java.lang.String)
at com.sun.xml.internal.ws.fault.SOAP11Fault.getProtocolException(SOAP11Fault.java:171)
at com.sun.xml.internal.ws.fault.SOAPFaultBuilder.createException(SOAPFaultBuilder.java:94)
at com.sun.xml.internal.ws.client.sei.SyncMethodHandler.invoke(SyncMethodHandler.java:240)
at com.sun.xml.internal.ws.client.sei.SyncMethodHandler.invoke(SyncMethodHandler.java:210)
at com.sun.xml.internal.ws.client.sei.SEIStub.invoke(SEIStub.java:103)
at $Proxy33.verify(Unknown Source)
I.
Let's start with a regular Java class with an operation that throws an exception, like this:
public class CheckVerify {
public MessageResponseType verify(CheckType check)
throws MyException { ... }
}
Because annotations are so easy in JAX-WS, I would expect that I could turn such a class into a Web Service using annotations, have the deployment tool run wsgen and create the web service artifacts (WSDL and XML Schema) for me, which it would. I would expect that Java would handle the exception for me like it does everything else: it turns the regular method into an 'operation' in the WSDL--it should also turn the 'throws MyException' into a '<soap:fault name="MyException" use="literal"/>' in the WSDL too. It does.
So what's the problem?
The problem is that while this web service will deploy, this doesn't actually work for a generated Java client of that web service. You need to know a little more, and make use of an additional annotation and something called a fault bean. This article shows you how.
II.
When implementing a web service using Java and JAX-WS, you might want to code in a natural style that allows you to throw an exception from your service operation, and have the runtime translate that into a SOAP fault that is received on the client.
You can code exceptions in JAX-WS, but the documentation on the web for this is practically non-existent, which is strange because it's such a common thing for Java programmers to want to do. Sometimes you have no choice but to deal with a checked exception that an underlying library throws (IOException is common). We need to know how to deal with this.
Say we have the following web service--the annotated version of the class we started with above. It's called CheckVerify and it defines a single operation that makes sure that a check writing customer is not known to the bad check writer database:
@WebService(
serviceName="CheckVerifyService",
portName="CheckVerify",
targetNamespace="http://www.example.com")
public class CheckVerify {
@WebMethod(operationName="verify")
public @WebResult(name="checkVerifyResponse",
partName="checkVerifyResponse",
targetNamespace="http://www.example.com")
MessageResponseType
verify(
@WebParam(name="check",
partName="check",
targetNamespace="http://www.example.com")
CheckType check)
throws CheckVerifyFault {
//...
}
}
This operation throws a CheckVerifyFault. Say that our implementation of the verify method actually calls a second, remote web service that uses HTTPS to validate the check, or goes to a database using JDBC. There might be checked Java exceptions that we want to translate into a SOAP Fault to use to return to the client.
Here's the portType snippet of the WSDL that results from these annotations:
<portType name="CheckVerify">
<operation name="verify">
<input message="tns:verify" />
<output message="tns:verifyResponse" />
<fault message="tns:MyException" name="MyException" />
</operation>
</portType>
While the above code will deploy to a container such as WebLogic, it won't have the desired result on the client.
First of all, JAX-WS will create an exception class for us, which is nice. But the exception class will be called MyException_Exception on the client side. Doing what we want is a more complicated and obscure part of the JAX-WS model, and using exceptions is not quite as easy as other things in JAX-WS.
If we run wsimport before our automated test, then compile the generated code and run the test, we'll see what effect this code has on the client view. And we'll have a good basis for undersanding the client side of this.
First we know that we want to change the name of the generated exception to something more friendly. So let's use bindings file to customize the generated code. You can read more about this in Java SOA Cookbook, but if you're using Maven to run the build that executes your wsimport, you can put a file called bindings.xml in the src/jaxws folder. Give that file the following contents:
<?xml version="1.0" encoding="UTF-8"?>
<bindings
xmlns="http://java.sun.com/xml/ns/jaxws"
xmlns:wsdl="http://schemas.xmlsoap.org/wsdl/">
<bindings node="wsdl:definitions/wsdl:portType[@name='CheckVerify']/wsdl:operation[@name='verify']/wsdl:fault[@name='MyException']">
<class name="MyFault">
<javadoc>Exception generated during any operation with the service.</javadoc>
</class>
</bindings>
</bindings>
This uses XPath to find the node in the generated WSDL that matches the fault with the name of "MyException" and indicates that the generated class should be named MyFault.
JAX-WS will create two classes now: MyFault, which is the JAXB customization re-name of the MyException class, and a class called MyException. That class will be a bean that holds a message.
The generated MyFault class on the client looks like this:
@WebFault(name = "MyException", targetNamespace = "http://www.example.com")
public class MyFault extends Exception {
/**
* Java type that goes as soapenv:Fault detail element.
*/
private MyException faultInfo;
public MyFault(String message, MyException faultInfo) {
super(message);
this.faultInfo = faultInfo;
}
public MyFault(String message, MyException faultInfo, Throwable cause) {
super(message, cause);
this.faultInfo = faultInfo;
}
public MyException getFaultInfo() {
return faultInfo;
}
}
The class is annotated with @WebFault, but indicates in its 'name' attribute that it is called MyException, which is the bean class (shown below) that we were renaming in our JAXB XML customization file.
So this is where things start getting weird. Notice that a MyFault constructor takes a MyException type. There is not a no-arg constructor in MyFault. The faultInfo (of type MyException) is expected to contain the SOAP Fault detail information. That is, there is a class (here, MyException, that holds the data element value of the <detail> element child of the <soap:fault> in the underlying SOAP envelope--the exception class is not doing that work for us.
So the first thing to understand is that MyException is not an exception type at all, but a regular bean that acts as a holder for the SOAP Fault information, to ensure it complies with that contract (because a SOAP Fault is not the same as a Java checked exception; checked exceptions don't exist in many languages). Put another way, even if you coded your exception to include a message String along with a getter and setter for it that could hold this info, JAX-WS doesn't know about it, so you need to define it explicitly.
So here is that generated 'exception' class, which is really not an exception at all, and rather a bean that your MyFault class (which actually IS the client-side exception class) accepts.
@XmlAccessorType(XmlAccessType.FIELD)
@XmlType(name = "MyException", propOrder = {
"message"
})
public class MyException {
protected String message;
public String getMessage() {
return message;
}
public void setMessage(String value) {
this.message = value;
}
} //comments deleted
Because the WSDL is valid and JAX-WS generated these classes for you, you might think that everything would work at this point. But it won't. Let's write the naive unit test we expect would work around this code on the client.
public class CheckVerifyServiceTest {
public static final Logger LOGGER = Logger.getLogger(CheckVerifyServiceTest.class);
private static final String WSDL_URL_KEY = "checkVerify.wsdl.url";
private static final QName QNAME = new QName(
"http://www.example.com", "CheckVerifyService");
private static CheckVerify checkVerify;
private static final String TODAY;
@BeforeClass
public static void setup() throws Exception {
LOGGER.debug("Attempting to initiate service...");
String wsdl = System.getProperty(WSDL_URL_KEY);
LOGGER.debug("Using WSDL: " + wsdl);
final URL wsdlUrl = new URL(wsdl);
final CheckVerifyService service = new CheckVerifyService(wsdlUrl, QNAME);
checkVerify = service.getCheckVerify();
if (checkVerify != null) {
LOGGER.debug("Found service stub.");
} else {
fail();
}
}
@Test
public void testMe() {
LOGGER.debug("Executing...");
CheckType check = new CheckType();
//....
MessageResponseType response = null;
try {
response = checkVerify.verify(check);
LOGGER.debug("Completed Exception response.");
} catch (MyFault ex) {
LOGGER.error("Error class: " + ex.getClass().getName());
LOGGER.error("Caught error. Message: " + ex.getMessage());
assertTrue(true);
}
}
}
In our @BeforeClass annotated method we use the generated service stub to get the port type for the service, and use that in our tests.
In the @Test method we invoke the verify method that throws the MyException exception (which we renamed to MyFault using JAXB customizations). So we put a try/catch around it and we should be able to expect that if we do something to make the method throw a fault/exception, that it would be caught here and our assertion would pass and everything would be fine, right? Wrong. The above code doesn't work.
NOTE: The proper way to positively test that your method throws an exception when it is supposed to in JUnit is with the @Test(expected=MyFault.class) annoation attribute. We want to look at the details here, so this is for illustration.
Here's what happens when you run the above test is that the test compiles but results in an ERROR (not a regular test failure):
testMe(com.dte.soa.checks.ws.CheckVerifyServiceTest) Time elapsed: 0.094 sec <<< ERROR!
javax.xml.ws.soap.SOAPFaultException: java.lang.NoSuchMethodException: com.dte.soa.checks.ws.jaxws.MyExceptionBean.setMessage(java.lang.String)
at com.sun.xml.internal.ws.fault.SOAP11Fault.getProtocolException(SOAP11Fault.java:171)
at com.sun.xml.internal.ws.fault.SOAPFaultBuilder.createException(SOAPFaultBuilder.java:94)
at com.sun.xml.internal.ws.client.sei.SyncMethodHandler.invoke(SyncMethodHandler.java:240)
at com.sun.xml.internal.ws.client.sei.SyncMethodHandler.invoke(SyncMethodHandler.java:210)
at com.sun.xml.internal.ws.client.sei.SEIStub.invoke(SEIStub.java:103)
at $Proxy33.verify(Unknown Source)
The problematic thing here is this line:
javax.xml.ws.soap.SOAPFaultException: java.lang.NoSuchMethodException: com.example.MyExceptionBean.setMessage(java.lang.String)
The problem appears to be that there is no method called setMessage in the MyExceptionBean class. This seems like a bug in JAX-WS, because we didn't write any class called MyExceptionBean, and it's not listed in our classes under the Generated Sources, so how could we possibly fix this?
The answer is to do this on the service side:
1. Write a class that extends Exception.
2. Annotate it with @WebFault and point its faultBean attribute to the name of a Java class that has a no-arg constructor, a message String field, and a getter and setter for it. This will be your carrier for that soap:fault detail element.
3. Redeploy your service and regenerate your client. Now the client view will be exactly how you want it. JAX-WS will take any message you supply to your exception type on the server side and put it in the SOAP Fault detail in the SOAP envelope. It will unpack that string message into the fault bean and make it available as the message within the exception you're catching.
Below are examples of using the @WebFault annotation to deal with SOAP faults and exceptions on the web service side.
We still write the method on the service exactly as before--there's no @WebFault annotation on the method--it's a class-level annotation.
@WebMethod(operationName="verify")
public @WebResult(name="checkVerifyResponse",
partName="checkVerifyResponse",
targetNamespace="http://www.example.com")
MessageResponseType
verify(
@WebParam(name="check",
partName="check",
targetNamespace="http://www.example.com")
CheckType check)
throws CheckVerifyFault {
Now we'll write that CheckVerifyFault class:
@WebFault(name="CheckVerifyFault",
targetNamespace="http://www.example.com")
public class CheckVerifyFault extends Exception {
/**
* Java type that goes as soapenv:Fault detail element.
*/
private CheckFaultBean faultInfo;
public CheckVerifyFault(String message, CheckFaultBean faultInfo) {
super(message);
this.faultInfo = faultInfo;
}
public CheckVerifyFault(String message, CheckFaultBean faultInfo, Throwable cause) {
super(message, cause);
this.faultInfo = faultInfo;
}
public CheckFaultBean getFaultInfo() {
return faultInfo;
}
}
This class extends Exception, and indicates that it takes a CheckFaultBean. So in Java web services we don't have the luxury of just using any checked exception that we have lying around; we'll have to annotate it, add the constructors that take our Bean info object, and then create that Bean info class. Here's what that looks like:
/**
* The Java type that goes as soapenv:Fault detail element.
* Used in web services exceptions, fault beans just hold the details
* of the SOAP fault. This one is used by the {@link CheckVerifyFault).
*
* @author eben hewitt
*/
public class CheckFaultBean {
private String message;
public CheckFaultBean() { }
public String getMessage() {
return message;
}
public void setMessage(String message) {
this.message = message;
}
}
Now you've got something that matches the SOAP semantics better, and we can recompile and redeploy the web service, then regenerate our client artifacts. Because I've renamed the exception class here, I'll need to update my bindings.xml file too, of course.
We also need to use this bean info in our service method. So instead of just throwing exception, we populate the fault bean with the data for the SOAP Fault Detail, and pass that into our exception; JAX-WS will handle the rest.
Here is our update service operation:
public MessageResponseType verify(CheckType check)
throws CheckVerifyFault {
LOGGER.debug("Executing verify method in Check web service.");
if (check == null) {
final String msg = "The Check cannot be null.";
LOGGER.debug(msg);
CheckFaultBean faultBean = new CheckFaultBean();
faultBean.setMessage(msg);
throw new CheckVerifyFault("This is the Basic Message.", faultBean);
}
//do work...
}
Notice that we create a bean instance, put our message for the client in that, then construct the exception and throw it. We can include two messages: the one associated with the exception string, and the more detailed message that is the string value of the FaultInfo object.
Here is the updated test case. It passes a null parameter to the verify method. That's just the condition the verify method is looking for to throw an exception:
@Test
public void testException() {
LOGGER.debug("Executing...");
CheckType check = new CheckType();
MessageResponseType response = null;
try {
response = checkVerify.verify(null);
LOGGER.debug("Completed Exception response.");
} catch (CheckVerifyFault ex) {
LOGGER.error("Error class: " + ex.getClass().getName());
LOGGER.error("Caught error. Message: " + ex.getMessage());
LOGGER.error("Detailed Message: " + ex.getFaultInfo().getMessage());
assertTrue(true);
}
}
Running this test gives us just the result that we want:
7/1/09-17:15 ERROR com.example.CheckVerifyServiceTest.testException - Error class: com.discounttire.checkverify.service.CheckVerifyFault
7/1/09-17:15 ERROR com.example.CheckVerifyServiceTest.testException - Caught error. Message: This is the Basic Message.
7/1/09-17:15 ERROR com.example.CheckVerifyServiceTest.testException - Detailed Message: The Check cannot be null.
...
BUILD SUCCESSFUL
------------------------------------------------------------------------
Total time: 10 seconds
Finished at: Wed Jul 01 17:15:54 MST 2009
Final Memory: 25M/153M
Now you can see that:
1. The test actually allows a totally natural client feel for using Java exceptions and SOAP faults.
2. The string that we populated the exception with on the service-side is readily visible to the client.
3. We get both the regular string message in the exception, and the detail in the fault info bean.
To read more about Java web services and SOAP, pick up a copy of my new book, Java SOA Cookbook from Amazon.