1

Given the following POJOs:

public class BaseEntity {
    public Long id;
    // ctor, getter & setter
}

public class Widget extends BaseEntity {
    public String fizz;
    public Boolean isBuzz;
    // ctor, getters & setters
}

I have the following client API for CRUDding Widget instances against a remote REST service:

public class WidgetServiceClient {
    public void createWidget(Widget w) {
        // RESTful call:    POST localhost/widgets
    }

    public Widget getWidgetById(Long id) {
        // RESTful call:    GET localhost/widgets/{id}
    }
}

And the following RESTful service endpoints exposed:

public class WidgetService {
    // Web server passes POST localhost/widgets/ calls to this method.
    public Widget createWidget(Widget w) {
        // Create the 'w' widget in the DB and return it with its DB-generated ID.
    }

    // Web server passes GET localhost/widgets/{id} calls to this method.
    public Widget getWidgetById(Long id) {
        // Ask the DB for the Widget with the passed-in 'id'. If it exist return it.
        // Otherwise return NULL.
    }
}

Let's pretend I've already figure out the "magic" of serializing/deserializing Widget instances to/from JSON.

This design is great, except when there is a server-side Exception that I want to communicate back to the client, RESTfully.

My first inclination was to modify BaseEntity to have a Throwable that could be used to communicate server-side errors back to the client-side:

public class BaseEntity {
    public Long id;
    public Throwable error;
    // ctor, getters & setters
}

So then:

public class WidgetService {
    // Web server passes POST localhost/widgets/ calls to this method.
    public Widget createWidget(Widget w) {
        try {
            // Create the 'w' widget in the DB and return it with its DB-generated ID.
        } catch(Throwable t) {
            w.setError(t);
        }

        return w;           
    }

    // Web server passes GET localhost/widgets/{id} calls to this method.
    public Widget getWidgetById(Long id) {
        Widget w = new Widget();
        try {
            // Ask the DB for the Widget with the passed-in 'id'. If it exist return it.
            // Otherwise return NULL.
        } catch(Throwable t) {
            w.setError(t);
        }

        return w;           
    }
}

But this feels kludgy/hacky, and I'm wondering if the other denizens of Javaland have already figured out a better approach/strategy to this problem. I happen to be using Jersey & Jackson for REST/serialization, but I'm thinking the solution should probably be framework-agnostic.

It also doesn't help when the service returns NULLs, which can happen.

So I ask: How can I pass Widget instances back and forth between client and server, RESTfully, but still allow the server to return NULLs and Exceptions/Throwables?

smeeb
  • 27,777
  • 57
  • 250
  • 447

2 Answers2

2

I suggest keeping a model response and an error response separate - separation of concerns. Assuming Jersey, Jersey understands how to suck the response out of your WebApplicationExceptions allowing you to provide rich error information in your error responses that help your client understand what went wrong.

As a brief example, you can leave your signature as returning Widget and throw WebApplicationException derived classes on error. Your client will receive a 200 Widget on success and 404 Response on exception (e.g. below).

// Web server passes GET localhost/widgets/{id} calls to this method.
public Widget getWidgetById(Long id) {
    Widget w = new Widget();
    try {
        // Ask the DB for the Widget with the passed-in 'id'. If it exist return it.
        // Otherwise return NULL.
    } catch(NotFoundException e) {
        throw new NotFoundException(Response.status(Response.Status.NOT_FOUND)
                .entity("Widget " + id + " not found.").build());
    } catch(Exception e) {
        throw new WebApplicationException(Response
                .status(Response.Status.INTERNAL_SERVER_ERROR)
                .entity("I don't know what happened, but trying again won't help: "
                        + e.getMessage())
                .build());

    }

    return w;           
}

NOTE: Only the Response is returned to the client unless you define a custom ExceptionMapper

NOTE: Instead of catching Throwable, your code will be more readable if you handle specific exceptions independently. Above, I have mapped every java exception to a general Jersey internal server error.

Steve Tarver
  • 3,030
  • 2
  • 25
  • 33
  • Thanks @starver (+1) - with the 404 Response will the JSON still include the `entity` (`e.getMessage()`)? – smeeb Oct 28 '14 at 10:06
  • Yes. The client's Response object will contain the status code and the entity. When the client does not receive a success code, they can Response.readEntity(String.class) to read your error message. Frequently, error response objects are strings, maps of key-value pairs, or custom rich error objects, depending on the robustness of your API. Which ever you pick, the error response entity should be consistent across the API. – Steve Tarver Oct 28 '14 at 19:58
1

I think you might want to use Jaxrs mechanism @Provider: JAX-RS jersey ExceptionMappers User-Defined Exception

@Provider
public class UserNotFoundMapper implements
    ExceptionMapper<UserNotFoundException> {
    @Override
     public Response toResponse(UserNotFoundException ex) {
         return Response.status(404).entity(ex.getMessage()).type("text/plain")
            .build();
    }
}
Community
  • 1
  • 1
  • Thanks @Kescha Skywalker (+1) - although this might work, I was sort of hoping for a more abstract/generic approach that was framework-agnostic. Any ideas? – smeeb Oct 27 '14 at 23:15
  • Hmm if you go completely agnostic, than you could reimplement the @Provider logic. Or you catch the exception on the level above the Service and store the exception directly back in your Widget like you suggested. I like to define a separate error type, but this depend on your requirements and scope – Kescha Skywalker Oct 27 '14 at 23:28