Http Status Codes for RESTful Web Services
You can use the standard Http status codes to indicate the types of errors than can occur. For example:
| Status Code | Cause | Resolution |
|---|---|---|
| 200 OK | The request was handled successfully. | |
| 400 Bad request | The request contains invalid parameters, parameter values or data. | Retry the call with correct parameters and data. |
| 401 Unauthorized | The caller has not been authenticated. | Authenticate and retry. |
| 403 Forbidden | The caller does not have the correct role to call this webservice. | Ensure that the caller has authenticated correctly and has the correct roles. |
| 404 Not found | No data was found for the parameters or data in the request. | |
| 406 Not acceptable | The Content-type or Accept Http headers on the request are not compatible with the webservice call e.g. the Accept is set to text/plain but the webservice only supports application/json. | Retry the request with the correct headers. |
| 500 Internal server error | Unhandled failure in the application server. The response body should contain a message e.g. "There was an error at 2016-04-22 14:08:02.456.". The response must not contain stack traces etc. | Check the server logs for the timestamp in the response message. |
Spring Controller Example
Using Spring, this can be achieved by using a base controller to handle exceptions:
package uk.co.donaldhorrell.bookshop.controller;
import java.text.SimpleDateFormat;
import java.util.Date;
import org.apache.commons.lang.exception.ExceptionUtils;
import org.apache.log4j.Logger;
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.bind.MissingServletRequestParameterException;
/**
* Base Controller for the bookshop controller.
*
*
*/
public class AbstractBookshopController {
private final Logger logger = Logger.getLogger(AbstractBookshopController.class);
/**
* Handler for the BookNotFoundException exception.
*
* @param ex
* @return exception message.
*/
@ResponseBody
@ExceptionHandler(BookNotFoundException.class)
@ResponseStatus(value=HttpStatus.NOT_FOUND)
public final String handleBookNotFoundException(BookNotFoundException ex) {
return ex.getMessage();
}
/**
* Handler for the MissingServletRequestParameterException.
*
* @param ex
* @return exception message.
*/
@ResponseBody
@ExceptionHandler(MissingServletRequestParameterException.class)
@ResponseStatus(value=HttpStatus.BAD_REQUEST)
public final String handleMissingServletRequestParameterException(MissingServletRequestParameterException ex) {
return ex.getMessage();
}
/**
* Handler for the IllegalArgumentException.
*
* @param ex
* @return exception message.
*/
@ResponseBody
@ExceptionHandler(IllegalArgumentException.class)
@ResponseStatus(value=HttpStatus.BAD_REQUEST)
public final String handleIllegalArgumentException(IllegalArgumentException ex) {
return ex.getMessage();
}
/**
* Handle internal server errors.
* Just return a timestamp, which can be checked in the application logs.
*
* @param ex
* @return exception message.
*/
@ResponseBody
@ExceptionHandler(Throwable.class)
@ResponseStatus(value=HttpStatus.INTERNAL_SERVER_ERROR)
public final String handleThrowable(Throwable t) {
logger.error("AbstractBookshopController.handleThrowable() : Exception["+t.getClass().getName()+"] message["+t.getMessage()+"] stack["+ExceptionUtils.getStackTrace(t)+"].");
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS");
return ("An error occured at "+sdf.format(new Date())+". Please contact support, quoting the full timestamp.");
}
}
The main controller would then look like this:
package uk.co.donaldhorrell.bookshop.controller;
import java.util.List;
import org.apache.log4j.Logger;
import uk.co.donaldhorrell.bookshop.service.BookshopService;
import uk.co.donaldhorrell.bookshop.domain.Book;
import uk.co.donaldhorrell.bookshop.domain.Books;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.MediaType;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;
/**
* Controller component that provides restful web services for the bookshop.
*
*
*/
@Controller
public class BookshopController extends AbstractBookshopController{
private final Logger logger = Logger.getLogger(BookshopController.class);
/**
* Bookshop business service component.
*
*/
@Autowired
private BookshopService bookshopService;
/**
* Get the list of books for an ISBN.
*
* @return list of books.
*/
@RequestMapping(method=RequestMethod.GET, value="/books/{isbn}", produces={MediaType.APPLICATION_JSON_VALUE, MediaType.TEXT_XML_VALUE, MediaType.APPLICATION_XML_VALUE})
@ResponseBody
public Documents getBooks(@PathVariable("isbn") String isbn) {
if (logger.isDebugEnabled()) {
logger.debug("BookshopController.getbooks().");
}
List result = bookshopService.getBooks();
if (logger.isDebugEnabled()) {
logger.debug("BookshopController.getBooks() : Found["+result.size()+"] books for isbn["+isbn+"].");
}
if (result.isEmpty()) {
throw new BookNotFoundException("No books found for isbn["+isbn+"].");
}
return(new Books(result));
}
}