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()."); } Listresult = 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)); } }