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 CodeCauseResolution
200 OKThe request was handled successfully. 
400 Bad requestThe request contains invalid parameters, parameter values or data.Retry the call with correct parameters and data.
401 UnauthorizedThe caller has not been authenticated.Authenticate and retry.
403 ForbiddenThe 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 foundNo data was found for the parameters or data in the request. 
406 Not acceptableThe 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 errorUnhandled 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));
    }
}