How to treat exception thrown by setter on JAXB automatic unmarshal from request body?

java, jax-rs, jaxb, rest, web-services

Solution

Interception Exceptions with JAXB

A `ValidationEventHandler` is used to intercept the exceptions that occur during unmarshalling. If you want to collect all the errors that occur you can use a `ValidationEventCollector`.

Java Model

import javax.xml.bind.annotation.XmlRootElement;

@XmlRootElement
public class Foo {

    private String bar;

    public String getBar() {
        return bar;
    }

    public void setBar(String bar) {
        throw new RuntimeException("Always throw an error");
    }

}

Demo

import java.io.StringReader;
import javax.xml.bind.*;
import javax.xml.bind.util.ValidationEventCollector;

public class Demo {

    private static String XML = "<foo><bar>Hello World</bar></foo>";

    public static void main(String[] args) throws Exception {
        JAXBContext jc = JAXBContext.newInstance(Foo.class);

        Unmarshaller unmarshaller = jc.createUnmarshaller();
        unmarshaller.unmarshal(new StringReader(XML));

        ValidationEventCollector vec = new ValidationEventCollector();
        unmarshaller.setEventHandler(vec);
        unmarshaller.unmarshal(new StringReader(XML));
        System.out.println(vec.getEvents().length);
    }

}

Output

1

Integrating with JAX-RS

`MessageBodyReader`

You could create an instance of `MessageBodyReader` where you can leverage a `ValidationEventHandler` during the unmarshal process. Below is a link giving an example of how to implement a `MessageBodyReader`.

- Validate JAXBElement in JPA/JAX-RS Web Service

`ContextResolver<Unmarshaller>`

Slightly higher level, you could implement `ContextResolver<Unmarshaller>` and have the `Unmarshaller` returned have the appropriate `ValidationEventHandler` on it. It would look something like the following:

import javax.ws.rs.core.*;
import javax.ws.rs.ext.*;
import javax.xml.bind.*;
import javax.xml.bind.helpers.DefaultValidationEventHandler;

@Provider
public class UnmarshallerResolver implements ContextResolver<Unmarshaller> {

    @Context Providers providers;

    @Override
    public Unmarshaller getContext(Class<?> type) {
        try {
            ContextResolver<JAXBContext> resolver = providers.getContextResolver(JAXBContext.class, MediaType.APPLICATION_XML_TYPE);
            JAXBContext jaxbContext;
            if(null == resolver || null == (jaxbContext = resolver.getContext(type))) {
                jaxbContext = JAXBContext.newInstance(type);
            }
            Unmarshaller unmarshaller = jaxbContext.createUnmarshaller();
            unmarshaller.setEventHandler(new DefaultValidationEventHandler());
            return unmarshaller;
        } catch(JAXBException e) {
            throw new RuntimeException(e);
        }
    }

}

Problem

I send the following JSON request body to my controller: ``` {"Game": {"url": "asd"}} ``` where `Game` is my model class, annotated with `@XmlRootElement` (and some JPA annotations which are not important in this context). The controller: ``` @PUT @Path("/{name}") @Consumes(MediaType.APPLICATION_JSON) @Produces(MediaType.APPLICATION_JSON) public Response createRow( @PathParam("name") String name, Game gameData) throws Exception{ Game.createRow(gameData); // + exception handling etc. } ``` Now, I understood that when `Game gameData` parameter of the controller method is created, my setters from the model class are called. The setter that requires attention is: ``` public void setUrl(String url) throws Exception{ String regex = "^(https?|ftp|file)://[-a-zA-Z0-9+&@#/%?=~_|!:,.;]*[-a-zA-Z0-9+&@#/%=~_|]"; Pattern pattern = Pattern.compile(regex); System.out.println("URL: " + url); if ( url == null || url.length() == 0) { throw new Exception("The url of the game is mandatory!"); } else { Matcher matcher = pattern.matcher(url); if (!matcher.matches()) { throw new Exception("The url is invalid! Please check its syntax!"); } else { this.url = url; } } } ``` What happens is that, during the deserialization of the JSON string to the Game object, the `The url is invalid!` exception is thrown, but only in the console of `TomEE`. What I want to do is to send this error to the client. If I use an exception which extends `WebApplicationException` instead of the generic Exception, then I get an exception in the client, but not the one about the validity of the url. Instead, after the deserialization, `gameData.url` is NULL, and when I try to create a Game instance with the data from `gameData`, the setter will be called like `gameToBeCreated.set(NULL)`, and I will get the exception `Url is mandatory`, when actually an URL was sent from the client, but with bad syntax. It was not NULL when sent from client. So, can I somehow intercept the exceptions thrown when the automatic unmarshalling happens and forward them to the client?

Original source

Related problems