Returning Backend Errors to the Caller/Frontend

We have implemented different methods for almost all backend endpoints to return errors to the caller/frontend. Very few of these support multilingual error messages, and the format also varies from endpoint to endpoint.

This best practice describes how an error that occurs at a backend endpoint should be returned to the frontend/caller and how the frontend should then access this error response to display it.

Example Error Response

What we want in the event of an error is an error response that is always structured the same way, like this example:

{
    "error": {
        "code": 33001,
        "name": "IM_UNSUPPORTED_OPERATION",
        "message": "Get users is not supported by this identity provider: oidc-keycloak",
        "messageKey": "CORE_ERROR_IDENTITY_PROVIDER_GET_USERS_UNSUPPORTED",
        "properties": {
        	"idp": "oidc-keycloak"
        }
    }
}

The actual error message message wird vom Backend Core abhängig von der Sprache des Benutzers generiert. Dazu wird über den <INLINE_CODE_1/>` is the language-specific text read from the language files and filled in with the properties.

de.json
...
"CORE_ERROR_IDENTITY_PROVIDER_GET_USERS_UNSUPPORTED": "Abfrage der Benutzer wird vom Identity Provider nicht unterstützt: {idp}"
en.json
...
"CORE_ERROR_IDENTITY_PROVIDER_GET_USERS_UNSUPPORTED": "Get users is not supported by this identity provider: {idp}"

Backend

It has proven effective to generate the error response via exceptions rather than creating it on the fly and returning it to the caller. So, in places where, for example, a ` queryId is missing, throw an exception with the information about what is missing, rather than generating and returning the error response directly here (see endpoints).

Furthermore, exceptions should not be caught and logged deeper in the code; this should only happen at the endpoint. That way, you have direct access to the caller and can analyze the problem much more easily using the stack trace.

Of course, there are exceptions to this rule: if, for example, IOExceptions, IllegalArgumentExceptions, etc., are thrown deeper in the code, it may make sense to catch them and instead throw a more descriptive ` de.virtimo.bpc.api.SystemException (see SystemException)

Endpoints

At every endpoint, all possible exceptions must be caught, logged, and the error response must be generated and returned via ` ErrorResponse.

Endpoint example
...
import de.virtimo.bpc.api.ErrorResponse;
...

@GET
@Path("/users")
@Produces({APPLICATION_JSON_UTF8})
@BpcRoleOrRightRequired(right = "IDENTITY_MANAGER_USERS_READ", role = "IDENTITY_MANAGER_ADMIN")
@BpcUserSessionRequired
public Response getUsers(
        @Context HttpHeaders hh
) {
    LOG.info("getUsers");
    try {
        return Response.ok(getIdentityManager().getUsers()).build();
    } catch (Exception ex) {
        LOG.log(Level.SEVERE, "Failed to fetch the list of users from the current IdP.", ex);
        return ErrorResponse.forException(ex).languageFrom(hh).build();
    }
}

ErrorResponse Class

As shown in the example above, the ` de.virtimo.bpc.api.ErrorResponse ` class is used to generate the JSON error response. It is structured according to the Builder pattern and provides the following methods.

forException(Exception)

Required. Set the exception here for which the error response should be generated.

usingTracker(BpcServicesTracker<ErrorResponseService>)

Optional, but highly recommended. The builder uses the ErrorResponseService in the background. If the tracker is not set, a tracker is created each time, the Service is retrieved, and the tracker is then discarded. This creates unnecessary overhead.

languageFrom(HttpHeaders)

Optional, but very useful if the message is to be language-dependent. The language to be used is read from the key ` X-BPC-Language ` in the passed HTTP headers. If the HTTP header is not found, ` en ` is used as the language key.

language(String)

Optional. Only useful if you need the message of a SystemException in a specific language (“de,” “en”).

SystemException with Error Code, HTTP Response Code, and Multilingual Support

This endpoint also generates a corresponding error response for “normal” exceptions. However, if this response is to support an error code, a specific HTTP response code, and multilingual support, then an ` de.virtimo.bpc.api.SystemException or a subclass thereof must be thrown.

SystemException call
throw new IdentityManagerException(CoreErrorCode.IM_UNSUPPORTED_OPERATION, "CORE_ERROR_IDENTITY_PROVIDER_GET_USERS_UNSUPPORTED", Map.of("idp", idpName));

If you do not want to generate a language-dependent error message, you can replace the key CORE_ERROR_IDENTITY_PROVIDER_GET_USERS_UNSUPPORTED with plain text.

IdentityManagerException.java
import de.virtimo.bpc.api.ErrorCode;
import de.virtimo.bpc.api.SystemException;

public class IdentityManagerException extends SystemException {
...
    public IdentityManagerException(ErrorCode errorCode, String message, Map<String, Object> props) {
        super(errorCode, message, props);
    }
...
}
CoreErrorCode.java
import de.virtimo.bpc.api.WebErrorCode;
import javax.ws.rs.core.Response;

public enum CoreErrorCode implements WebErrorCode {
...
    IM_UNSUPPORTED_OPERATION(33001, Response.Status.SERVICE_UNAVAILABLE),
...
}

The WebErrorCode is used to define a unique number, which may help support teams identify the nature of the problem more quickly when handling support requests. This also specifies which HTTP response code should be returned to the caller. For example, if a resource was not found: Response.Status.NOT_FOUND corresponds to HTTP response code 404.

Frontend

In the frontend, the error message can be accessed and displayed using a BpcCommon utility function.

failure : function (record, operation) {
   const errorMsg = BpcCommon.Util.getErrorFromFailureResponse(operation.error.response).message;
   BpcCommon.Api.showNotification({
      text : `${BpcCommon.Api.getTranslation("CORE_ERROR")}: ${errorMsg}`,
      toast       : true,
      toastTarget : window,
      type        : "ERROR"
   });
},

Keywords: