Considerations on Exception Creation
Reflections on when and how to create Exceptions in Java. Discusses fillInStackTrace costs and exception handling strategies.
Introduction
During development, careful consideration is needed about when and how to create exceptions (
Exception).
Generally, indiscriminate exception creation is not a good idea.
Conversely, not creating exceptions when needed can make debugging and error tracking difficult.
Key Considerations
1. Avoid Indiscriminate Exception Creation
- In code where you don’t need to
throwan error, think twice about creating anexception. - The cost of
exceptioncreation varies depending on the situation, but is generally high.
2. Understanding Exception Creation Costs
- When an exception occurs, costs arise from traversing the exception call stack to find a method that can handle the
exception. Exceptionis an implementation ofThrowable- Additional costs occur when collecting call stack information through the
fillInStackTrace()method - The process of traversing the call stack to gather class names, method names, line numbers, etc. to create a
stacktracealso contributes to increased costs
-> Note that the actual cost depends on the depth and size of the stack trace
- Additional costs occur when collecting call stack information through the
3. Consider Whether an Exception is Really Needed
As a somewhat extreme example, in the code below that checks memory, the current memory status check has stages:
Warning situation(log.warn()) - GC can free up enough memorySevere situation(log.error()) - Memory hasn’t been freed enough due to load, server might crash
In a situation where only a warn is sufficient, do you really need to create an exception?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// Inappropriate example
public void checkMemoryUsage() {
long usedMemory = totalMemory - freeMemory;
if (usedMemory > MEMORY_THRESHOLD) {
Exception memoryException = new Exception("Memory attention required!");
throw memoryException;
}
}
// Improved approach
public void checkMemoryUsage() {
long usedMemory = totalMemory - freeMemory;
if (usedMemory > MEMORY_THRESHOLD) {
log.warn("Memory attention required. Used memory: {}", usedMemory);
// Can send alerts via webhook, etc.
}
}
Improvements
So, if exception creation is needed but the cost is expected to be high, how can we improve it?
1. Override fillInStackTrace
- Override fillInStackTrace to eliminate the cost of creating a stacktrace
1
2
3
4
5
6
public class OptimizedException extends Exception {
@Override
public Throwable fillInStackTrace() {
return this; // Avoid stack trace generation cost
}
}
2. Cache Frequently Occurring Exceptions
1
2
3
public class ExceptionCache {
public static final CommonException COMMON_EXCEPTION = new CommonException("Common exception situation that occurs frequently");
}
Caveats
- OptimizedException or cached exceptions should only be used for exceptions where stack traces are not needed
- Exceptions that occur as part of the application’s normal flow - like validation stage exceptions
- Repeatedly occurring exceptions: Exceptions that frequently occur in the same situation
- For situations where actual problem tracking is needed, it’s better to use regular Exceptions.
- Performance-optimized exceptions might cause confusion during code review or debugging.
What’s Really Important
- It’s more important to clearly describe what the problem is when creating exceptions (same goes for
logging) - Code that just ends with
throw new Exception(e.getMessage())only makes tracking harder. - Since you’re the one who’ll be tracking the problem when it occurs… it’s important to write clearly.
- Additionally, before creating a
customException, consider whether you can clearly express it using the standardRuntimeExceptionsubclasses provided.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// Problematic code
try {
categoryService.update(id, requestDto));
} catch (Exception e) {
throw new RuntimeException(e.getMessage());
}
// Improved code
try {
log.info("[@{}] Category update request for id={}: {}", name, id, requestDto);
categoryService.update(id, requestDto));
} catch (Exception e) {
log.error("[@{}] Error occurred while updating category with id={}", name, id, e);
// If it was a bad argument, IllegalArgumentException might be better
throw new IllegalArgumentException("[@{}] Error occurred while updating category with id={}", name, id, e));
}
Reference
This post is licensed under CC BY 4.0 by the author.