Exception Handling in Java and Android

Image source

Java exceptions handling is one of the most fundamental things a developer should know by heart. Unfortunately, developers often underestimate the importance of exception handling. This article explains everything you need to know about exception handling in Java, including real-life examples.

What Is Exception Handling?

An exception is an event that disrupts the normal flow of a program execution. For example, unusual user inputs, or a file system error when reading or writing a file. Exception handling prevents a program from crashing by managing these situations.

When an error occurs in a Java method, the method creates an exception object and passes it over to the Java Virtual Machine (JVM) runtime system. Exception objects contain information such as the type and the state of the program at the moment when the error occurs. The process of creating an exception object and passing it to JVM is referred to as throwing an exception.

Exception management is an important part of the DevOps pipeline. The collaboration between developers and operations can improve the quality of exceptions, thus gaining improved software quality and preventing cyber attacks.

How Exceptions Work in JVM and Android

In Java, all exception and error types are subclasses of Throwable. The Throwable class changes the execution flow of JVM apps by throwing exceptions and deciding how to recover from an error.

For instance, the code below uses the throw statement to throw an IllegalStateException:

fun main() {

try {

throw IllegalStateException()

println("My Name is John")

} catch (exc: Throwable) {

println("Error")

}

}

.

Adding an exception to a code changes its execution flow. The string ‘My Name is John’ is never printed. Rather, the program jumps to the catch block, and executes the error recovery code. Eventually the program prints ‘Error’ instead of ‘My Name is John’.

In some cases, the program cannot recover from an error. For example, in OutOfMemoryError the only option is to restart the app and leave the Throwable as unhandled.

Throwable class structure

There are two direct subclasses of Throwable—Errors and Exceptions. Usually an Exception handles conditions where the recovery is possible, and Error is used for conditions where recovery is not possible.

Errors and Exceptions have their own subclasses. For example, IllegalStateException indicates that the program experienced an unexpected state.

Consider the code snippet from the previous section:

fun main() {

try {

throw IllegalStateException("Unexpected condition")

println("My Name is John")

} catch (exc: Throwable) {

println("Error")

}

}

The IllegalStateException object takes a snapshot of the application at the moment when an error occurs:

java.lang.IllegalStateException: Unexpected condition

at

com.instance.androidapp.Exceptions101Kt.main(Exceptions101.kt:6)

at

com.instance.androidapp.Exceptions101Kt.main(Exceptions101.kt: 9)

at

com.instance.androidapp.Exceptions101Kt.func(Exceptions101.kt:15)

Each line represents a single application call stack frame at the time of the error. Each frame includes the filename, method name, and the error line number.

Hierarchy of Java Exception Handling

The JVM must find an exception handler after the method throws an exception to avoid app crashing.

The Catch block

First up in the hierarchy is the catch block. Java catch block handles exceptions by declaring the exception type. The catch block comes after the try block. For example, the following code snippet handles throwables of type IllegalStateException

try {

crashBlock()

} catch (exc: IllegalStateException) {

}

The finally block

The finally  block contains code that the JVM must execute regardless of any exceptions. For example, when using the finally block to close an open connection to a database or file even if the try block throws an exception.

try {

crashBlock()

} catch (exc: IllegalStateException){

finally {

}

Handle all uncaught JVM exceptions

Next phase in the hierarchy is handling all uncaught JVM exceptions in the current Java thread. An uncaught exception is a situation where an exception is thrown after the handler was set. Uncaught exception handling is implemented with the UncaughtExceptionHandler interface.

The hierarchy for setting UncaughtExceptionHandler in different places is as follows:

  1. Invoke UncaughtExceptionHandler if a handler has been set on the current thread
  2. If not in the current thread, invoke exception handling on the ThreadGroup
  3. Invoke the the default handler that handles all uncaught JVM exceptions by printing a stack trace report, and then terminating the app.

Implementing an Uncaught Exception Handler in JVM

The goal is to create an exception handler that captures the stack trace for every unhandled error, and generates a diagnostic report.

Setting the UncaughtExceptionHandler as the default handler for all exceptions

fun main() {

val handlingExceptions = BasicExceptionHandler()

Thread.setDefaultUncaughtExceptionHandler(handlingExceptions)

throw RuntimeException("Error!")

}

Override the JVM's default implementation

class BasicExceptionHandler : Thread.UncaughtExceptionHandler {

override fun uncaughtException(thread: Thread, exc: Throwable) {

}

}

Add a report object to UncaughtExceptionHandler

class BasicExceptionHandler : Thread.UncaughtExceptionHandler {

override fun uncaughtException(thread: Thread, exc: Throwable) {

val report = Report(exc)

}

}

UncaughtExceptionHandler now generates a Report object that contains a stack trace for each unhandled error.

Delivering an error report

The next step is to add a Delivery object that delivers the report to a random location.

class BasicExceptionHandler : Thread.UncaughtExceptionHandler {

override fun uncaughtException(thread: Thread, exc: Throwable) {

val report = Report(exc)

delivery.deliver(report)

}

interface Delivery {

fun deliver(report: Report)

}

There are a lot of error conditions that you have to account for within the Delivery object. For example, caching reports locally when there is no network connectivity, and ensuring the handler does not make long-lived requests.

Conclusion

Java provides a sophisticated exception handling mechanism that enables you to detect and fix errors when they occur. The execution of a Java code is not terminated when an exception occurs. The code execution continues until completion once the exception is resolved.

Another advantage of exception handling in Java is the meaningful error reporting. Other programming languages use random error codes for error reporting. In Java, you can obtain a description of an exception in the form of a string and display the description using a println() statement.


Author Bio

Image source

Gilad David Maayan is a technology writer who has worked with over 150 technology companies including SAP, Samsung NEXT, NetApp and Imperva, producing technical and thought leadership content that elucidates technical solutions for developers and IT leadership.

LinkedIn: https://www.linkedin.com/in/giladdavidmaayan/