Skip to main content

Error Handling

The JAX-RS stack expanded by the rest-expanders has an extensive error handling mechanism which contains several layers of validation, as well as transformation of errors to a fixed standardized format. This errors are returned through the API based on the Problem Details format.

Error format

Errors returned from the API are formatted as follows:

{
"type": "string",
"title": "string",
"status": 0,
"identifier": "string",
"code": "string",
"extraInfo": {}
}
  • type: Contains a path intended to provide information or documentation about the error type.
  • title: A message describing the error.
  • status: The HTTP status code associated with the error.
  • identifier: A unique identifier for the error that occurred, which can be used to identify the error in the log files.
  • code: The code which identifies the specific error type.
  • extraInfo: An object which defines additional information about the error. This object can have a different structure for each error type, though by default only two variants are used, one for the majority of errors and one specifically for validation errors.

Server error types

There are several default server error types, each of which are categorized by HTTP status.

400 Bad request

TypeNameDescription
BR001BR_VALIDATIONA validation error was returned by one of the validation steps in the JAX-RS stack. This is error is the result of a call to the API containing invalid data. See validation for more information.
BR002BR_JSONA serialization error occurs in the JSON.org library.
BR003BR_JSON_MAPPINGA fatal error occurred in Jackson when trying to map a JSON object.
BR004BR_JSON_PROCESSINGA fatal error occurred in Jackson when trying to serialize a JSON object.
BR005BR_INVALID_FORMATA value could not be serialized because it has an invalid format.
BR006BR_JSON_PARSEA JSON object could not be parsed due to an invalid structure.
BR007BR_QUERY_PARAMAn error occurred while trying to parse a query parameter.
BR008BR_TIMESTAMP_FORMATAn error occurred due to a timestamp being provided in an incorrect format.
BR009BR_HEADER_PARAMAn error occurred while trying to parse a header parameter.
BR010BR_PATH_PARAMAn error occurred while trying to parse a path parameter.
BR011BR_COOKIE_PARAMAn error occurred while trying to parse a cookie parameter.
BR012BR_FORM_PARAMAn error occurred while trying to parse a form parameter.
BR013BR_MATRIX_PARAMAn error occurred while trying to parse a matrix parameter.
BR014BR_MALFORMED_HEADERA header was supplied with a value that did not match the expected format.
BR015BR_MALFORMED_SORT_PARAMETERSSorting parameters were passed to the API which did not match the expected format.
BR016BR_UNKNOWN_SORT_PARAMETERA sorting parameter which does not exist was passed to the API.
BR017BR_CUSTOM_VALUE_TYPE_FORMATA value for a custom ValueFieldType could not be correctly mapped.

401 Not authorized

TypeNameDescription
UA001UA_NOT_AUTHORIZEDReturned when the user for the call is not authorized to access the requested resource.

403 Forbidden

TypeNameDescription
FB001FB_FORBIDDENReturned when an authenticated user tries to interact with a resource for which it does not have authorization.
FB002FB_CORSThe client made a request that violates CORS policies enforced by the server.

404 Not found

TypeNameDescription
NF001NF_RESOURCEA resource was requested from an endpoint by its unique resource identifier and no resource matching this identifier was found (or authorized).
NF002NF_ENDPOINTAn endpoint which does not exist was called.

405 Method not allowed

TypeNameDescription
NA001NA_METHODAn HTTP method that is not available was used to call an endpoint.

406 Not Acceptable

TypeNameDescription
AC001AC_NOT_ACCEPTABLEReturned if the server cannot provide a response that was deemed acceptable based on the content negotiation process.
Example: The client requests a content type text/csv with the Accept header, on an endpoint for which the server only supports returning application/json.

415 Unsupported media type

TypeNameDescription
UM001UM_NOT_SUPPORTEDA payload was sent to the server with a content type that is not supported by that endpoint.
Example: The client sends a POST call, with a payload of content type text/csv, on an endpoint for which the server only accepts application/json.

500 Internal server error

TypeNameDescription
SE001SE_ILLEGAL_STATEAn unhandled IllegalStateException was thrown during the call.
SE002SE_SEARCH_FAILEDA SearchResultException was caught, which is thrown when a Diagnostic is returned to an endpoint of which the key ends with .findFailed.
SE003SE_CREATE_FAILEDA CreateFailedException was caught, which is thrown when a Diagnostic is returned to an endpoint of which the key ends with .createFailed.
SE004SE_MODIFICATION_FAILEDA ModificationFailedException was caught, which is thrown when a Diagnostic is returned to an endpoint of which the key ends with .modifyFailed.
SE005SE_DELETE_FAILEDA DeleteFailedException was caught, which is thrown when a Diagnostic is returned to an endpoint of which the key ends with .deleteFailed.
SE006SE_ILLEGAL_DB_STATE[DEPRECATED] An IllegalDBStateException was caught, which is thrown when a Diagnostic is returned to an endpoint of which the key is cruds.findIllegalDBState.
SE007SE_NULL_POINTERAn unhandled NullPointerException was thrown during the call.
SE008SE_METHOD_NOT_FOUNDThrown when method reflection fails. This can occur when JAX-RS has to dynamically call a Java method defined as part of the API implementation.
SE009SE_TRANSACTION_ROLLED_BACK[DEPRECATED] Thrown when an EJB transaction is rolled back.
SE010SE_ILLEGAL_ARGUMENTAn unhandled IllegalArgumentException was thrown during the call.
SE011SE_GENERICAn internal server error (InternalServerErrorException) was caught for which the cause could not specifically be identified.
SE012SE_HEADER_VALUEA HeaderValueException was caught, which indicates that the value of a header could not be read correctly.
SE013SE_UNKNOWN_RESPONSE_ERRORReturned for any error during the building of an API response for which the Exception could not be identified.
SE014SE_UNKNOWNReturned for any error during the processing of an API request for which the Exception could not be identified.

Custom error handlers

Custom error handlers can be added to extend the functionality of the REST API. These handlers can be used to catch both 3rd party exceptions, defined in the Java API or custom exceptions defined in the application. The exception classes should be available in the control layer for the handler to work correctly, otherwise a ClassNotFoundException could occur.

danger

When adding custom error types, make sure to choose the error code carefully. Do not simply append the next code in the sequence of already existing codes, as this will cause conflicts if new codes are added by the rest expanders. The application will fail at runtime if a collision occurs. Example: Don't choose AC002, as the REST expanders might add this code as well at some point. Instead, choose something with a unique prefix, such as CE001 (custom error 1) or CSE001 (custom internal server error 1).

A custom error handlers requires four elements:

  • A new error should be registered in the ServerError class by calling the register(status, code, message) method, which takes the HTTP status of the error, the unique error code and an unlocalized identifier for the error message. This can be added in the custom-errors anchors.
  • A translation for the error, mapping the message key to a human-readable sentence should be added to the ServerErrorMessages.properties file in the custom-translations anchors. Injecting parameters into these messages is not supported, unlike with validation errors.
  • If the error handler does not handle an existing exception, a custom exception class should be added.
    Example of a custom exception class
    package my.favorite.jaxrs.exception;

    import net.democritus.contract.NotNull;

    public final class ILoveException extends RuntimeException {

    public ILoveException(@NotNull String message) {
    super(message);
    }

    }
  • Finally, an exception handler should be added. This can be a subclass of ExceptionMapper<E extends Throwable> for exceptions that occur when processing an incoming request, or a subclass of ResponseErrorMapper to handle exceptions that occur while processing the response. Usually it will be the first of these two. The class should have a javax.ws.rs.ext.Provider annotation in order for Jersey to pick it up and add it to the REST pipeline.
    Example of a custom exception handler
    package myComponent.jaxrs.provider.error;

    import myComponent.jaxrs.response.RestErrorResponse;
    import myComponent.jaxrs.response.error.ServerError;
    import myComponent.jaxrs.response.error.ServerErrorInfoModel;

    import my.favorite.jaxrs.exception.ILoveException;

    import net.palver.logging.Logger;
    import net.palver.logging.LoggerFactory;

    import javax.servlet.http.HttpServletRequest;
    import javax.ws.rs.container.ResourceContext;
    import javax.ws.rs.core.Context;
    import javax.ws.rs.core.Response;
    import javax.ws.rs.ext.ExceptionMapper;
    import javax.ws.rs.ext.Provider;

    import java.util.*;

    @Provider
    public class ILoveExceptionHandler implements ExceptionMapper<ILoveException> {

    private static final Logger logger = LoggerFactory.getLogger(ILoveExceptionHandler.class);

    @Context
    private ResourceContext resourceContext;

    /**
    * Sets the {@link ResourceContext} for the call. This is only injected automatically if the handler is called
    * directly from the JAX-RS stack.
    *
    * @param resourceContext The resource context object.
    * @return The handler instance to chain invocations.
    */
    public ILoveExceptionHandler setResourceContext(ResourceContext resourceContext) {
    this.resourceContext = resourceContext;
    return this;
    }

    @Override
    public Response toResponse(ILoveException exception) {
    Response response = RestErrorResponse.builder()
    .setError(ServerError.CE_I_LOVE_ERRORS)
    .setExtraInfo(mapException(exception))
    .build();

    logException((RestErrorResponse) response.getEntity(), exception);

    return response;
    }

    private ServerErrorInfoModel mapException(ILoveException exception) {
    ServerErrorInfoModel errorModel = new ServerErrorInfoModel();
    errorModel.setMessage(exception.getLocalizedMessage());

    return errorModel;
    }

    private void logException(RestErrorResponse entity, ILoveException exception) {
    HttpServletRequest servlet = resourceContext.getResource(HttpServletRequest.class);

    StringBuilder logMessage = new StringBuilder()
    .append("[")
    .append(entity.getCode())
    .append(" - ")
    .append(entity.getIdentifier())
    .append("] ")
    .append(servlet.getMethod())
    .append(" ")
    .append(servlet.getPathInfo())
    .append(servlet.getQueryString() == null ? "" : "?" + servlet.getQueryString())
    .append(": ")
    .append(entity.getTitle())
    ;
    if (logger.isInfoEnabled()) {
    logger.info(
    logMessage.toString()
    );
    }
    }

    }