How to customize JAX-WS RuntimeException responses?

226 views Asked by At

I am trying to customize the fault responses for RuntimeExceptions thrown before reaching to the WebMethod, like invalid xml body or wrong method name for a JAX-WS service. I tried to use a handler with @HandlerChain but the response is sent before processed by the handler.

Here is an example. Lets say i have a method copyTool method in my ws but client sent below request. I want to be able to customize the response so that i can change the faultString or return some custom faultCode.

Input

<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/"
xmlns:cor="http://acme.com/">
   <soapenv:Header/>
   <soapenv:Body>
      <cor:copyTool>
       <toolNumber>some Text instead of Integer</toolNumber>
      </cor:copyTool>
   </soapenv:Body>
</soapenv:Envelope>

Output

    <S:Envelope xmlns:S="http://schemas.xmlsoap.org/soap/envelope/">
   <S:Body>
      <ns0:Fault xmlns:ns0="http://schemas.xmlsoap.org/soap/envelope/" xmlns:ns1="http://www.w3.org/2003/05/soap-envelope">
         <faultcode>ns0:Server</faultcode>
         <faultstring>Exception Description: The object [some Text instead of Integer], of class [class java.lang.String], from mapping [org.eclipse.persistence.oxm.mappings.XMLDirectMapping[toolNumber-->toolNumber/text()]] with descriptor [XMLDescriptor(com.acme --> [DatabaseTable(ns0:copyTool)])], could not be converted to [class java.lang.Integer].
Internal Exception: java.lang.NumberFormatException: For input string: "some Text instead of Integer"</faultstring>
      </ns0:Fault>
   </S:Body>
</S:Envelope>
2

There are 2 answers

1
Joan On

I think you can try with an ExceptionMapper as follows:

@Provider
public class WebApplicationExceptionMapper implements ExceptionMapper<Exception>{

    @Override
    public Response toResponse(Exception exception) {
        Response.Status responseStatus = THE_STATUS_YOU_WANT_BASED_ON_THE_EXCEPTION;
        return Response.status(responseStatus).entity(THE_ERROR_ENTITY_YOU_WANT_BASED_ON_THE_EXCEPTION).build();
    }

}
5
Anish B. On

I was able to customize the fault message via @HandlerChain for RuntimeException. I guess you did something wrong.

Step - by - Step process on what I did to make it working:

I have created a sample project to demonstrate.

Project Structure (Maven Project):

enter image description here

pom.xml:

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>org.example</groupId>
    <artifactId>customize-fault-jaxws</artifactId>
    <version>1.0-SNAPSHOT</version>

    <properties>
        <maven.compiler.source>17</maven.compiler.source>
        <maven.compiler.target>17</maven.compiler.target>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    </properties>

    <dependencies>
        <dependency>
            <groupId>javax.xml.ws</groupId>
            <artifactId>jaxws-api</artifactId>
            <version>2.3.1</version>
        </dependency>
        <dependency>
            <groupId>com.sun.xml.ws</groupId>
            <artifactId>jaxws-rt</artifactId>
            <version>2.3.7</version>
        </dependency>
    </dependencies>

</project>

PublishLotteryService:

package org.example.endpoint;

import org.example.webservice.LotteryServiceImpl;

import javax.xml.ws.Endpoint;

public class PublishLotteryService {

    public static void main(String[] args) {
        Endpoint.publish("http://localhost:8080/webservice/lottery", new LotteryServiceImpl());
    }

}

LotteryService (A WebService Interface):

package org.example.webservice;

import javax.jws.WebMethod;
import javax.jws.WebService;
import javax.jws.soap.SOAPBinding;
import javax.jws.soap.SOAPBinding.Style;
import javax.jws.soap.SOAPBinding.Use;

@WebService
@SOAPBinding(style = Style.DOCUMENT, use = Use.LITERAL)
public interface LotteryService {

    @WebMethod(operationName = "SendYourNameAndWinLottery")
    String sendYourNameAndWinLottery(String nameOfThePerson);

}

LotteryServiceImpl (A WebService Implementation):

package org.example.webservice;

import javax.jws.HandlerChain;
import javax.jws.WebService;

@WebService(endpointInterface = "org.example.webservice.LotteryService")
@HandlerChain(file = "custom-fault-message.xml")
public class LotteryServiceImpl implements LotteryService {

    @Override
    public String sendYourNameAndWinLottery(String nameOfThePerson) {
        return "Hi, " + nameOfThePerson + ". You are one of the lucky winners. You won a $1000 USD.";
    }
}

Note: You have to tell your WebService to use the @HandlerChain via creating a handler chain .xml file with any name and setting/referring that .xml file into the @HandlerChain.

custom-fault-message.xml

<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<javaee:handler-chains 
     xmlns:javaee="http://java.sun.com/xml/ns/javaee" 
     xmlns:xsd="http://www.w3.org/2001/XMLSchema">
  <javaee:handler-chain>
    <javaee:handler>
      <javaee:handler-class>org.example.handler.CustomizeFaultMessageHandler</javaee:handler-class>
    </javaee:handler>
  </javaee:handler-chain>
</javaee:handler-chains>

CustomizeFaultMessageHandler:

package org.example.handler;

import javax.xml.namespace.QName;
import javax.xml.soap.SOAPBody;
import javax.xml.soap.SOAPException;
import javax.xml.soap.SOAPFault;
import javax.xml.ws.handler.MessageContext;
import javax.xml.ws.handler.soap.SOAPHandler;
import javax.xml.ws.handler.soap.SOAPMessageContext;
import java.util.Set;

public class CustomizeFaultMessageHandler implements SOAPHandler<SOAPMessageContext> {

    @Override
    public boolean handleMessage(SOAPMessageContext context) {
        return true;
    }

    @Override
    public boolean handleFault(SOAPMessageContext context) {
        try {
            SOAPBody body = context.getMessage().getSOAPPart().getEnvelope().getBody();
            SOAPFault soapFault = body.getFault();
            soapFault.setFaultCode("random-code");
            soapFault.setFaultString("place your custom error message here.");
        } catch (SOAPException e) {
            System.out.println(e.getMessage());
        }
        return true;
    }

    @Override
    public void close(MessageContext context) {
    }

    @Override
    public Set<QName> getHeaders() {
        return null;
    }

}

Few things to be noted which are important:

You have to set return true; from false in the handleMessage(SOAPMessageContext context). Otherwise, it won't go into handleFault(SOAPMessageContext context) to handle Fault related things.

Now, you can see this code :

SOAPBody body = context.getMessage().getSOAPPart().getEnvelope().getBody();
SOAPFault soapFault = body.getFault();
soapFault.setFaultCode("random-code");
soapFault.setFaultString("place your custom error message here.");

Just I'm getting the default Fault and overriding it with my custom Fault Code and Fault Message here. Now, the current SOAPMessageContext is updated with the Fault containing my custom values. That's all you have to do.

Output (Success Case):

Input XML Request Payload :

<Envelope xmlns="http://schemas.xmlsoap.org/soap/envelope/">
    <Body>
        <SendYourNameAndWinLottery xmlns="http://webservice.example.org/">
            <arg0 xmlns="">Anish</arg0>
        </SendYourNameAndWinLottery>
    </Body>
</Envelope> 

Output XML Response:

<?xml version='1.0' encoding='UTF-8'?>
<S:Envelope xmlns:S="http://schemas.xmlsoap.org/soap/envelope/">
    <S:Body>
        <ns2:SendYourNameAndWinLotteryResponse xmlns:ns2="http://webservice.example.org/">
            <return>Hi, Anish. You are one of the lucky winners. You won a $1000 USD.</return>
        </ns2:SendYourNameAndWinLotteryResponse>
    </S:Body>
</S:Envelope>

enter image description here

Output (Failure With Custom Fault Message):

I have tampered the soap body name SendYourNameAndWinLottery to an incorrect name SendYourNameAndWin for testing.

Incorrect XML Request Payload:

<Envelope xmlns="http://schemas.xmlsoap.org/soap/envelope/">
    <Body>
        <SendYourNameAndWin xmlns="http://webservice.example.org/">
            <arg0 xmlns="">Anish</arg0>
        </SendYourNameAndWin>
    </Body>
</Envelope> 

Output XML Response:

<?xml version='1.0' encoding='UTF-8'?>
<S:Envelope xmlns:S="http://schemas.xmlsoap.org/soap/envelope/" xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/">
    <SOAP-ENV:Header/>
    <S:Body>
        <S:Fault xmlns="" xmlns:ns3="http://www.w3.org/2003/05/soap-envelope">
            <faultcode xmlns="">random-code</faultcode>
            <faultstring>place your custom error message here.</faultstring>
        </S:Fault>
    </S:Body>
</S:Envelope>

enter image description here