Exceptions are objects that are thrown when an error or unexpected situation occurs during the execution of a program. In other to provide a smooth user experience Exception Handling should not be overlooked as they are a way to handle runtime errors in a program.
This guide will cover everything from the basic of what Exception Handling is, why we need them, and how to effectively use Exception Handling to provide a great experience. Let's get started!
Bonus: There are a few practice questions at the end of the writing that will help you prepare for your next interview and evaluate your knowledge.
TL;DR: Exceptions are errors in a program, and handling them is crucial for a smooth user experience. This guide covers the basics of what they are, why we need them, and how to use them effectively.
Table of Contents
What and Why of Exception Handling
Basics of Exception Handling
How to Effectively Practice Exception Handling
Best Practices and Tips for Exception Handling
Interview/Practice Questions
Conclusion
What and Why of Exception Handling
Exception Handling is a mechanism that is used to handle errors or unexpected behaviour that may occur during the execution of a program. By using exception handling, developers can create programs that gracefully handle errors and provide useful feedback to users.
Exception Handling is very important in a program because they provide a way of handling errors to ensure stability and prevent runtime error which can cause the program to crash or display runtime error messages to the user which only the programmer should see.
When executing a program and an error occurs, the Java Virtual Machine create an exception object and throws it to the user, it’s the responsibility of the programmer to catch the error and handle the exception in a way that does not disrupt the normal flow of the program and at the same time communicate the error properly to the user.
Basics of Exception Handling
Type Of Exception
There are three kinds of exceptions in Java.
Checked Exception: These exceptions are caused by an external force beyond the control of the program e.g IOException,SQLException,FileNotFoundException etc. They are checked by the compiler at compile-time and must be handled in the application. Example.
public class FileWriteExample { public static void main(String args[]) { FileWriter file = null; try { file = new FileWriter("example.txt"); // This may throw an IOException file.write("Hello, world!"); } catch (IOException e) { // Handling the checked exception System.out.println("An error occurred while writing to the file"); } finally { try { file.close(); } catch (IOException e) { System.out.println("An error occurred while closing the file"); } } } }
Unchecked Exception: These exceptions are associated with the logic of the program. They are not checked by the compiler .example include ArithmeticException, NullPointerException etc.
In the first part of ExceptionHandlingSampleA above, an exception will be thrown when trying to divide the age 12 with zero. The exception that was thrown was handled using the try-and-catch block.
//filename: ExceptionHandlingSampleA.java public class ExceptionHandlingSampleA { public static void main(String[] args) { try{ int age= 12; int result = age/0; }catch (ArithmeticException e){ System.out.println("An error occurred: "+ e.getMessage()); } } } //filename: ExceptionHandlingSampleB.java public class ExceptionHandlingSampleB { public static void main(String[] args) { int age= 12; int result = age/0; } }
However in the ExceptionHandlingSampleB, we are not handling the exception, so when the program is run it will break its flow and return an “Exception in thread "main" java.lang.ArithmeticException: / by zero “ and also disrupts the flow of the program.
Error: These exceptions are not subject to Catch or specify Requirements in the sense that they are external to the application.
The try-catch block
This is an approach used to handle exceptions in Java. The code that may throw an exception is placed in the try block and the code that handles the exception is placed inside the catch block.
public static void main(String[] args) {
try{
int age= 12;
int result = age/0;
}catch (ArithmeticException e){
//code that handles the exception
System.out.println("An error occurred: "+ e.getMessage());
}
}
C. Throwing exceptions: You can also manually throw an exception using the throw
keyword. This is useful when you want to create your own custom exceptions or handle unexpected events in your code.
Consider the below code we are manually throwing a built-i exception whenever we encounter a zero as the divisor.
public class ExceptionHandlingSampleC {
public static void main(String[] args) throws ArithmeticException{
halfAge(10,2);
halfAge(10,0);
}
public static void halfAge(int num1,int num2)throws ArithmeticException {
if (num2 == 0) {
throw new ArithmeticException("Cannot divide by zero");
}
int result = num1 / num2;
System.out.println("Result: " + result);
}
}
D. The finally block: The finally block is executed regardless of whether an exception was thrown or not. It is used to release resources or close files.
How to Effectively Practice Exception Handling
A. Using multiple catch blocks
Using multiple catch blocks in exception handling helps in handling different exceptions in different ways. In Java, we can have multiple catch blocks for a single try block, and each catch block can handle a specific type of exception. By using multiple catch blocks, we can handle different exceptions gracefully and improve the overall robustness of our program.
public class ExceptionHandlingSampleE {
public static void main(String[] args) {
try {
// some code that may throw an exception
FileInputStream file = new FileInputStream("myFile.txt");
} catch (IOException e) {
// handle IOException
System.out.println("An I/O Exception occurred: " + e.getMessage());
} catch (NullPointerException e) {
// handle NullPointerException
System.out.println("A NullPointerException occurred: " + e.getMessage());
} catch (Exception e) {
// handle any other exception
System.out.println("An unexpected Exception occurred: " + e.getMessage());
}
}
}
B. Creating custom exceptions
In Java, we can create our own custom exceptions by extending the Exception class or one of its subclasses. This is useful when we want to handle a specific type of exception in a unique way or when we want to create a new type of exception that is not covered by the built-in exception classes.
class NegativeExceptionHandler extends Exception {
public NegativeExceptionHandler(String message) {
super(message);
}
}
class ExceptionHandlingSampleD {
public static void checkNumber(int num) throws NegativeExceptionHandler {
if (num < 0) {
throw new NegativeExceptionHandler("Number cannot be negative");
}
System.out.println("Number is positive");
}
public static void main(String[] args) throws NegativeExceptionHandler {
checkNumber(-1);
}
}
C. Rethrowing exceptions
In some cases, we may want to catch an exception, do some processing, and then rethrow the exception so that it can be handled by the caller or a higher-level exception handler. This is useful when we want to add some additional information to the exception or when we want to convert one type of exception into another.
class MyInputException extends Exception {
public MyInputException(String message, Throwable cause) {
super(message, cause);
}
}
class ExceptionHandlingSampleF {
public static void printAge() throws MyInputException {
Scanner scanner = new Scanner(System.in);
System.out.print("Enter your age: ");
try {
int num = scanner.nextInt();
System.out.println("You are " + num +"years old");
} catch (InputMismatchException e) {
throw new MyInputException("Invalid input, please enter a number", e);
} finally {
scanner.close();
}
}
public static void main(String[] args) throws MyInputException {
try {
printAge();
} catch (Exception e) {
// handle exception
throw new MyInputException("An error occurred", e);
}
}
}
D. Exception propagation and the call stack:
When an exception is thrown, Java searches for the first catch block that can handle the exception by looking at the catch blocks in the order they are written in the code. If no catch block is found, the exception is propagated up the call stack until it reaches the top-level exception handler, which can either handle the exception or terminate the program.
class MyCallStackException extends Exception {
public MyCallStackException(String message) {
super(message);
}
}
class ExceptionHandlingSampleG {
public static void main(String[] args) {
ExceptionHandlingSampleG example = new ExceptionHandlingSampleG();
try {
example.method1();
} catch (MyCallStackException e) {
System.out.println("Caught exception: " + e.getMessage());
e.printStackTrace();
}
}
public void method1() throws MyCallStackException {
try {
method2();
} catch (Exception e) {
throw new MyCallStackException("An error occurred");
}
}
public void method2() throws MyCallStackException {
throw new MyCallStackException("Exception in method2");
}
}
In this example, if method2()
throws a MyCallStackException
, it will be caught by the catch block in method1()
, which will then rethrow it as a MyCallStacException
with additional information. The exception will then propagate up the call stack until it reaches the top-level exception handler.
Best Practices and Tips for Exception Handling
A. Avoiding overly broad catch blocks It is important to avoid overly broad catch blocks that catch all exceptions. This can make it difficult to identify the specific exception that caused the problem. Instead, catch only the exceptions that you can handle and let the others propagate up the call stack.
try {
// some code that may throw exceptions
} catch (Exception e) {
// avoid catching all exceptions with a broad catch block
// handle specific exceptions instead
// log the error message
}
B. Using specific exception types It is important to use specific exception types instead of catching the general Exception class. This helps to make the code more readable and easier to maintain. For example, if you are trying to read a file and an IOException occurs, catch that specific exception instead of catching Exception.
try {
// some file I/O code that may throw exceptions
} catch (FileNotFoundException e) {
// handle file not found exception
// log the error message
} catch (IOException e) {
// handle general I/O exception
// log the error message
}
C. Logging exceptions Logging exceptions is a good practice as it provides valuable information for debugging and troubleshooting. It is recommended to log the exception along with a description of the error and the context in which it occurred.
try {
// some code that may throw exceptions
} catch (Exception e) {
// log the exception message and stack trace
logger.error("Exception caught: " + e.getMessage(), e);
}
D. Handling checked and unchecked exceptions Java has two types of exceptions - checked and unchecked. Checked exceptions are those that the compiler forces you to handle, either by catching them or declaring them in the method signature. Unchecked exceptions are those that do not require handling. It is good practice to handle checked exceptions and let unchecked exceptions propagate up the call stack.
// handling checked exception using try-catch block
try {
// some code that may throw a checked exception
} catch (IOException e) {
// handle the IOException
}
// handling unchecked exception at runtime
try {
// some code that may throw an unchecked exception
} catch (ArithmeticException e) {
// handle the ArithmeticException
}
Interview/Practice Questions
What is an unchecked exception in Java, and why is it important to handle them properly?
Write a Java method that throws a custom exception if a given input string is null or empty. Provide a code sample to demonstrate how you would call this method and handle the exception.
What is the difference between a checked and unchecked exception in Java, and when would you use one over the other?
Write a Java code snippet to demonstrate how to use the try-catch-finally block to handle exceptions.
Can you explain how exception propagation works in Java? How does it relate to the call stack?
Write a Java code snippet to demonstrate how to re-throw an exception and explain why you might want to do this.
Can you explain how to use the multi-catch block in Java, and when would you want to use it?
Write a Java code snippet to demonstrate how to handle multiple exceptions using separate catch blocks for each exception.
In a multi-threaded Java application, how would you handle exceptions that occur in one thread but need to be logged or handled by another thread? Provide a code sample to demonstrate your solution.
Write a Java code snippet to demonstrate how to use the try-with-resources block to handle exceptions when working with resources that need to be closed. Explain how this differs from using a try-catch-finally block to handle the same scenario.
Conclusion
we have covered the basics of what Exception Handling is, why it is important, and how to effectively use it to provide a great user experience. We have also discussed best practices and tips for Exception Handling, along with practice questions to help you evaluate your knowledge and prepare for interviews. By implementing these strategies, developers can create programs that gracefully handle errors and provide valuable feedback to users, ensuring stability and preventing runtime errors that can disrupt the normal flow of the program.
Thanks for reading :)