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 you should not do it just for testing if 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)
-
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 that we propose as solution.
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();
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 already provides ready-to-use implementations for this such as RestServiceExceptionFacade. The exception facade has to…
-
log all errors (user errors on info and technical errors on error level)
-
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.
-
The following errors may occur in any devon application:
Code | Message | Link |
---|---|---|
|
An unexpected error has occurred! We apologize any inconvenience. Please try again later. |
|
|
«original message of the cause» |