Architecture & DesignHow to Use Exceptions Effectively in Java

How to Use Exceptions Effectively in Java

Developer.com content and product recommendations are editorially independent. We may make money when you click on links to our partners. Learn More.

Java Exception is a mechanism to handle abnormal situations that may occur during program execution. Exceptional codes are encompassed within a try…catch block so that, if exception occurs in the course of program flow, it may be tackled programmatically rather than resulting in crashes. This article walks through some concepts, codes, and scenarios to broadly understand how it can be used effectively in Java programs.

Java Exception

The exception handling mechanism of Java is heavily influenced by the work Exception Handling for C++ of Andrew Koenig and Bjarne Stroustrup. The simplest way to realize the real concept behind the existence of the exception handling mechanism in programming languages is to understand ‘divide by zero exception’. Simply put, what the program is supposed to do or behave in the course of the code if there is a situation where a number is divided by zero. Note that ‘divide by zero’ is a well-known mathematical concept which states that when any number is divided by zero, the result is undefined. So, if such a situation occurs in the code, the program is bound to terminate abnormally. But, not so, if there is a way to handle the situation and give the code, say, another chance, an ability to recover or terminate gracefully. Grossly, this is all about the concept of exception handling in Java.

Now, let’s try a very rudimentary example to illustrate a ‘divide by zero’ program without handling any exceptions.

package com.mano.examples;
import java.util.Scanner;
public class Main {
   public static void main(String[] args) {
      do {
         Scanner input = new Scanner(System.in);
         System.out.printf("nEnter numerator: ");
         int num = input.nextInt();
         System.out.printf("nEnter denominator: ");
         int denom = input.nextInt();
         int quo = num / denom;
         System.out.printf("nResult = " + quo);
         System.out.printf("nDo you want to continue (y/n):");
         String choice = input.next();
         if(!choice.trim().equals("y"))
            break;
      }while(true);
   }
}

The output is as follows:

Enter numerator:
10
Enter denominator:
0
Exception in thread "main" java.lang.ArithmeticException: / by zero
   at com.mano.examples.Main.main(Main.java:15)

As we execute the program and give an input value of 0 as the denominator, the program terminates abnormally with ArithmeticException. The ArithmeticException is an extension of RuntimeException thrown when an exceptional arithmetic condition occurs; as we can see in our case, we tried to divide a number by zero. There are many such exception classes to take care of exceptional situations in Java program, ArithmeticException is just one among them for that specific purpose.

Now, the question is: Can we improve the code and give it another chance to execute? The answer is yes, we can do so by appropriately handling the exception.

package com.mano.examples;
import java.util.InputMismatchException;
import java.util.Scanner;
public class Main {
   public static void main(String[] args) {
      do {
         Scanner input = new Scanner(System.in);
         try {
            System.out.printf("nEnter numerator: ");
            int num = input.nextInt();
            System.out.printf("nEnter denominator: ");
            int denom = input.nextInt();
            int quo = num / denom;
            System.out.printf("nResult = " + quo);
         }catch (InputMismatchException ex1){
            System.err.println("Sorry, input does not match.
               Try again.");
            input.nextLine();
            System.err.println("Enter only numbers.");
         }catch(ArithmeticException ex2){
            System.err.println("Please enter a non-zero
               denominator. Try again.");
            ex2.printStackTrace();
         }finally {
            System.out.printf("nDo you want to continue
               (y/n):");
            String choice = input.next();
            if (!choice.trim().equals("y"))
               break;
         }
      }while(true);
   }
}

Notice that here in the code we handled multiple exceptions: one, ArithmeticException for the ‘divide by zero’ problem and another, InputMismatchException, to handle the situation where the input given is a strict integer and not a string or float value. Multiple catches take care of both the situation and displays appropriate messages. The finally block executes in either case, whether an exception occurs or not does not matter. Therefore, here we put the choice whether the user wants to try again or not. A ‘y’ input would make the loop continue again and any other input would cause the code to break from the infinite loop.

The crux of the matter is that with, appropriate handling of the exception, we are able to get an abnormal situation under control and let the program terminate in a graceful manner.

Ignoring Exceptions

Exceptions are nothing but events signaled during a program’s execution. The goal of this event is to obstruct the normal execution flow. In earlier programming languages such as C, a very rudimentary version of this kind was error codes and execution status checking. Exception handling took the idea further and provided a solid mechanism to tackle the situation.

Java does not make it compulsory to encompass code within a try…catch block. We can literally throw every exception that occurs in the program without handling any of them as follows.

public class Main {
   public static void main(String[] args) throws Throwable{

      // ...
   }
}

What it does is simply ignore any exceptions that occur in the program and throws them. But, this is not a good idea. If we are to write a robust code, we should never ignore an exception. If possible, we must log them so that later on, when inspecting the log file, we may get crucial clues to pinpoint what is wrong with the code. There is really a rare circumstance where we may ignore any exceptions.

Exceptions vs Errors

All Java exception classes are a direct or indirect subclass of Exception, which is the top class in the exception hierarchy. We can extend this hierarchy to create our own custom exception classes. Throwable is the superclass of Exception and has two subclasses: Exception and Error. The Exception class, and its subclasses, is used to represent exceptional situations in a Java program that may be handled appropriately. The Error class and its subclasses, on the other hand, represent abnormal conditions that occur in the JVM. Exceptions are more frequent than Errors and the later should not be caught by applications. Exceptions are recoverable, whereas Errors are fatal.

Checked and Unchecked Exceptions

There are two classes of exceptions in Java: checked and unchecked.

Checked exceptions represent exceptional situations that are beyond immediate control of the program such as memory, file system, or network exceptions. They are subclasses of Exception and must be handled explicitly. The most common unchecked exception is the IOException.

Unchecked exceptions are those which signal error conditions about program logic, assumptions made such as null pointers, unsupported operations, invalid arguments, and so forth. They typically are a subclass of RuntimeException and may not be handled explicitly. The most common unchecked exception is the NullPointerException.

Structurally, Java does not make any distinction between checked and unchecked exceptions.

Try-with-resource

The try-with-resource is better than the try…catch and finally blocks. In any major try…catch block, there is a block called finally, which typically contains the cleanup code. For every socket, file, or database connection opened, it must be closed appropriately, whether an exception occurs or not does not matter. Therefore, these cleanup codes are usually written within the finally block. As a result, the code in the finally block often looks very cluttered and dirty.

Connection con = null;
try {
   con = DriverManager.getConnection(DB_URL,
      USER_NAME,PASSWORD);
   // ...
}catch(Exception ex){
   ex.printStackTrace();
}finally{
   if(con!=null){
      try {
         con.close();
      }catch (SQLException ex){
      }
   }
}

In the preceding code snippet, we tried to close the database connection in the finally block.

There is a better way to write the same code. The try-with-resource construct significantly simplifies the code structure and can be used to manage the resource appropriately as follows.

try(Connection con = DriverManager.getConnection
         (DB_URL,
      USER_NAME,PASSWORD)){
   Class.forName("com.mysql.jdbc.Driver");

   // ...
}catch(Exception ex){
   ex.printStackTrace();
}

Note that Java does many things behind the scenes with the try-with-resource to make the code work perfectly, including resource management. The code looks much cleaner and should be used more often than the try…catch and finally blocks.

Lambdas and Exceptions

Lambda expression in Java makes the code concise and clean, with less boilerplate code. But, an exception with the try…catch expression actually professes an elaborate description of the code. Hence, exceptions do not align well with lambda expression. However, there are provisions where Java code written in the form of lambda expression also can throw an exception, provided that the exception is compatible with the specification of the functional interface. The functional interface method must specify the exception type or its supertype. Therefore, exception handling with lambda expression is solely dependent upon how the functional interface decides to handle the exception. If it does not specify the exception, a lambda expression cannot throw it.

Let’s try a very rudimentary and simple example to prove the point. Try to compile the following code. It will not compile and will complain about Unhandled Exception:java.lang.Exception.

package com.mano.examples;
public interface FunctInt {
   int add(int a, int b);
}
package com.mano.examples;
public class Main {
   public static void main(String[] args) {
      FunctInt f = (a,b)->{
         int c = a + b;
         throw new Exception();
      };
      //...
   }
}

But, as we add throws Exception in the functional interface method, the code compiles without error.

package com.mano.examples;
public interface FunctInt {
   int add(int a, int b) throws Exception;

}

Conclusion

These are some of the key points to ponder when writing exception handling effectively. Understand that exceptions are an indication of a problematic situation that may occur in the course of program execution. Handling them programmatically gives you an opportunity to resolve them at the earliest, possibly without causing an adverse domino effect or program crash. If handled appropriately, an application in execution may continue to execute without major consequence. Therefore, exception handling is a major step in writing fault-tolerating programs that can not only deal with its own problems and carry on its affairs but also terminate in a graceful manner.

Get the Free Newsletter!

Subscribe to Developer Insider for top news, trends & analysis

Latest Posts

Related Stories