Skip to content

Latest commit

 

History

History
107 lines (74 loc) · 7.01 KB

guide-exceptions.asciidoc

File metadata and controls

107 lines (74 loc) · 7.01 KB

Exception Handling

Exception Principles

For exceptions we follow these principles:

  • We only use exceptions for exceptional situations and not for programming control flows, etc. Creating an exception in Java is expensive and hence should not be done for simply testing whether something is present, valid or permitted. In the latter case design your API to return this as a regular result.

  • We use unchecked exceptions (RuntimeException) [1]

  • We distinguish internal exceptions and user exceptions:

    • Internal exceptions have technical reasons. For unexpected and exotic situations, it is sufficient to throw existing exceptions such as IllegalStateException. For common scenarios a own exception class is reasonable.

    • User exceptions contain a message explaining the problem for end users. Therefore, we always define our own exception classes with a clear, brief, but detailed message.

  • Our own exceptions derive from an exception base class supporting

All this is offered by mmm-util-core, which we propose as a solution. If you use the devon4j-rest module, this is already included. For Quarkus applications, you need to add the dependency manually.

If you want to avoid additional dependencies, you can implement your own solution for this by creating an abstract exception class ApplicationBusinessException extending from RuntimeException. For an example of this, see our Quarkus reference application.

Exception Example

Here is an exception class from our sample application:

public class IllegalEntityStateException extends ApplicationBusinessException {

  private static final long serialVersionUID = 1L;

  public IllegalEntityStateException(Object entity, Object state) {

    this((Throwable) null, entity, state);
  }


  public IllegalEntityStateException(Object entity, Object currentState, Object newState) {

    this(null, entity, currentState, newState);
  }

  public IllegalEntityStateException(Throwable cause, Object entity, Object state) {

    super(cause, createBundle(NlsBundleApplicationRoot.class).errorIllegalEntityState(entity, state));
  }

  public IllegalEntityStateException(Throwable cause, Object entity, Object currentState, Object newState) {

    super(cause, createBundle(NlsBundleApplicationRoot.class).errorIllegalEntityStateChange(entity, currentState,
        newState));
  }

}

The message templates are defined in the interface NlsBundleRestaurantRoot as following:

public interface NlsBundleApplicationRoot extends NlsBundle {


  @NlsBundleMessage("The entity {entity} is in state {state}!")
  NlsMessage errorIllegalEntityState(@Named("entity") Object entity, @Named("state") Object state);


  @NlsBundleMessage("The entity {entity} in state {currentState} can not be changed to state {newState}!")
  NlsMessage errorIllegalEntityStateChange(@Named("entity") Object entity, @Named("currentState") Object currentState,
      @Named("newState") Object newState);


  @NlsBundleMessage("The property {property} of object {object} can not be changed!")
  NlsMessage errorIllegalPropertyChange(@Named("object") Object object, @Named("property") Object property);

  @NlsBundleMessage("There is currently no user logged in")
  NlsMessage errorNoActiveUser();

Handling Exceptions

For catching and handling exceptions we follow these rules:

  • We do not catch exceptions just to wrap or to re-throw them.

  • If we catch an exception and throw a new one, we always have to provide the original exception as cause to the constructor of the new exception.

  • At the entry points of the application (e.g. a service operation) we have to catch and handle all throwables. This is done via the exception-facade-pattern via an explicit facade or aspect. The devon4j-rest module already provides ready-to-use implementations for this such as RestServiceExceptionFacade that you can use in your Spring application. For Quarkus, follow the Quarkus guide on exception handling.
    The exception facade has to …​

    • log all errors (user errors on info and technical errors on error level)

    • ensure that the entire exception is passed to the logger (not only the message) so that the logger can capture the entire stacktrace and the root cause is not lost.

    • convert the error to a result appropriable for the client and secure for Sensitive Data Exposure. Especially for security exceptions only a generic security error code or message may be revealed but the details shall only be logged but not be exposed to the client. All internal exceptions are converted to a generic error with a message like:

      An unexpected technical error has occurred. We apologize any inconvenience. Please try again later.

Common Errors

The following errors may occur in any devon application:

Table 1. Common Exceptions
Code Message Link

TechnicalError

An unexpected error has occurred! We apologize any inconvenience. Please try again later.

TechnicalErrorUserException.java

ServiceInvoke

«original message of the cause»

ServiceInvocationFailedException.java


1. Whether to use checked exceptions or not is a controversial topic. Arguments for both sides can be found under The Trouble with Checked Exceptions, Unchecked Exceptions — The Controversy, and Checked Exceptions are Evil. The arguments in favor of unchecked exceptions tend to prevail for applications built with devon4j. Therefore, unchecked exceptions should be used for a consistent style.