Throwing a exception through through multiple layers of an application can add some more or less useful information.
Exception enhancement
Whenever it is necessary to catch an exception in order to add some additional information to the exception and re-throw it,
no new exception is created rather then the original exception is enhanced with some context information.
Exception Wrapping
Catch a exception and throw a new exception with additional information and the original exception.
This results usually in a long and ‘stacked’ stack trace, but most of the time, only the stack trace of
the original exception is interesting. The other stacktraces are just noise.
Terminology
A system failure is an event that occurs when the delivered service deviates from correct service.
A system may fail either because it does not comply with the specification, or because the specification
did not adequately describe its function. An error is that part of the system state that may cause
a subsequent failure: a failure occurs when an error reaches the service interface and alters the service.
A fault is the adjudged or hypothesized cause of an error.
(https://softwareengineering.stackexchange.com/questions/184412/whats-the-difference-between-fault-error-and-defect/184418)
An easy way to implement exception enhancement is to use a ContextedRuntimeException
class
like https://github.com/LeoNiedermeier/io.github.leoniedermeier.utils/blob/master/src/main/java/io/github/leoniedermeier/utils/excecption/ContextedRuntimeException.java:
Printing a ContextedRuntimeException
gives a message of the kind:
ErrorCodes: CODE_1, CODE_3
CID: QQAWFR
---------------------------------
Descriptions:
CODE_1: Description CODE 1
CODE_3: Description CODE 3
---------------------------------
Exception Context:
ErrorCode: CODE_1
CID: QQAWFR
label: MyValue
ErrorCode: CODE_3
---------------------------------
at io.github.leoniedermeier.utils.excecption.ContextedRuntimeExceptionTest$ThrowsException.doSomething(ContextedRuntimeExceptionTest.java:25)
at io.github.leoniedermeier.utils.excecption.ContextedRuntimeExceptionTest.lambda$1(ContextedRuntimeExceptionTest.java:44)
...
The output consists of the following elements:
The correlation id (CID) is unique per ContextedRuntimeException
instance. For instance the CID can help to trace the exception
through remote calls and their logs.
In order to have a unique identifier for the exception type, the constructor of ContextedRuntimeException
requires an ErrorCode
.
public interface ErrorCode {
String code();
...
}
Note that there can be more additional error codes added (via ContextedRuntimeException#addErrorCode(ErrorCode)
). This can be useful in the case of exception enhancement.
In general the error codes are implemented as enums in the root package of the module. The enums have to implement EnumErrorCode
.
In this case the error code is the name of the enum constant.
enum MyErrorCodes implements EnumErrorCode {
MISSING_FILE,
WRONG_INPUT;
}
The context values are a list of label-value pairs. Label is a String
, value an Object
.
Note that there can be multiple elements with the same label.
The context values can provide additional information in order to support problem diagnosis.
try {
...
} catch (Exception e) {
throw new ContextedRuntimeException(MY_ERROR_CODE, e)
.addContextValue("Account Number", accountNumber)
.addContextValue("Amount Posted", amountPosted)
.addContextValue("Previous Balance", previousBalance)
}
private void dataBase()
{
throw new ContextedRuntimeException(MY_ERROR_CODE)
.addContextValue("query", "select * from table");
}
public void service() {
try {
dataBase();
}
catch(ContextedRuntimeException exception) {
// enhance the current exception and rethrow it
throw exception.addContextValue("Service", "Context information of service");
}
}
ExceptionContext
or ExtendedExceptionContext
.getContextEntries
has to return a list where the context Entry
s can be stored.getMessage
should delegate to getFormattedExceptionMessage(...)
in order to print the context
values in the message.public class CustomContextedRuntimeException extends RuntimeException
implements ExtendedExceptionContext<ContextedRuntimeException> {
/** Where the data is stored. */
private final List<Entry> entries = new ArrayList<>();
@Override
public List<Entry> getContextEntries() {
return entries;
}
/**
* Provides the message explaining the exception, including the contextual data.
*
* @see java.lang.Throwable#getMessage()
* @return the message, never {@code null}.
*/
@Override
public String getMessage() {
return getFormattedExceptionMessage(super.getMessage());
}
...
}
The @HttpResponseStatus
annotation (
https://github.com/LeoNiedermeier/io.github.leoniedermeier.utils/blob/master/src/main/java/io/github/leoniedermeier/utils/web/advice/HttpResponseStatus.java)
adds a http response status (org.springframework.http.HttpStatus
) to the error code enum.
enum MyErrorCodes implements EnumErrorCode {
ERROR_CODE_SIMPLE,
@HttpResponseStatus(HttpStatus.NOT_FOUND)
ERROR_CODE_WITH_HTTP_NOT_FOUND;
}
See https://spring.io/blog/2013/11/01/exception-handling-in-spring-mvc
The http response status of the error code enum of the ContextedRuntimeException
can be evaluated in a controller advice,
for instance
https://github.com/LeoNiedermeier/io.github.leoniedermeier.utils/blob/master/src/main/java/io/github/leoniedermeier/utils/web/advice/RestExceptionHandler.java
public ResponseEntity<ErrorInformation> handle(ContextedRuntimeException exception) {
ErrorInformation errorInformation = ...;
errorInformation.setStatus(exception.findLastErrorCode().filter(ec -> ec instanceOf Enum<?>)
// (enum) field with same id as code() returns
.map(ec -> findField(ec.getClass(), ec.code()))
.map(field -> field.getAnnotation(HttpResponseStatus.class))//
.map(HttpResponseStatus::value).orElse(HttpStatus.INTERNAL_SERVER_ERROR));
...
}