Let's Think a Little Bit About Exceptions
What Is an Exception?
An exception is an event that occurs unexpectedly and disrupts the normal flow of a program. We never write code thinking, "This should be a good exception!" On the contrary, exceptions happen when something goes wrong beyond our control or planning.
Therefore, handling exceptions properly is crucial to ensuring that our system remains robust and reliable.
Checked vs. Unchecked Exceptions
Whether you're using Java or Kotlin, exceptions are an essential part of the language. Let's explore the hierarchy behind exception handling.
Checked Exceptions
Checked exceptions are exceptions that are verified at compile time. This means that the compiler forces you to handle them either by using a try-catch
block or by propagating them with the throws
keyword. The base class for checked exceptions is Exception
(excluding RuntimeException
and its subclasses).
Examples: IOException
, SQLException
, FileNotFoundException
.
Unchecked Exceptions
Unchecked exceptions occur during runtime and are not checked at compile time. They usually arise due to logical errors in the program, such as accessing a null reference or an invalid index. The base class for unchecked exceptions is RuntimeException
.
Examples: NullPointerException
, ArrayIndexOutOfBoundsException
, IllegalArgumentException
.
Handling and Throwing Exceptions
In Java, you can either handle exceptions directly or propagate them to the caller. Letβs look at both approaches.
Handling Exceptions
The most common way to handle exceptions is by using a try-catch-finally
block:
try {
yourMethod();
// Additional logic
} catch (IOException e) {
// Log the stack trace or throw a custom exception
throw new CustomException("Error executing the method", e);
} finally {
// Cleanup resources, such as closing files or database connections
}
π‘ Best Practice: Avoid catching general exceptions like Exception
. Instead, catch specific exceptions to make your error handling more precise.
Throwing Exceptions
Instead of handling an exception within the method, you can propagate it to the caller using the throws
keyword:
public void yourMethod() throws IOException {
Files.readAllLines(Paths.get("file.txt"));
}
This approach allows the calling method to decide how to handle the exception.
Unit Test Example
A unit test can validate that a method correctly throws an exception using JUnit:
import static org.junit.jupiter.api.Assertions.*;
import org.junit.jupiter.api.Test;
class ExceptionTest {
@Test
void testExceptionIsThrown() {
ExceptionThrowingClass obj = new ExceptionThrowingClass();
assertThrows(IllegalArgumentException.class, () -> {
obj.methodThatThrowsException();
});
}
}
This test ensures that methodThatThrowsException
correctly throws an IllegalArgumentException
when executed.
Final Thoughts
Handling exceptions properly makes your code more reliable and maintainable. When choosing between checked and unchecked exceptions, think about the best way to communicate failures to users and developers. Also, whenever possible, prefer throwing custom exceptions to make error handling clearer and more meaningful.
Happy coding! π
Top comments (0)