In java we have already pre-defined exception classes to trigger on pre-defined conditions such as when you divide a number by zero it triggers ArithmeticException, when you perform some operation on ‘null’ it triggers NullPointerException, when we try to access invalid index on array it trigger ArrayIndexOutOfBoundsException … etc. But Java doesn’t trigger an exception when business rule is violated. Let’s discuss with an example.
Example 01 : To understand the need of custom exception
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 |
package com.vidvaan.corejava.exception23.customexception; //To understand the need of custom exception public class Example01NeedOfCustomException { public static void main(String[] args) { BankAccount obj = new BankAccount(); System.out.println("Before withdraw Balance : " + obj.balanceEnquiry()); obj.withDrawAmount(2000); System.out.println("After withdraw Balance : " + obj.balanceEnquiry()); } } class BankAccount { private int accountBalance = 1000; public void withDrawAmount(int withDrawalAmount) { // Here we are violating business rule if withDrawalAmount > accountBalance // But JVM doesn't create exception when business rule is violating here accountBalance = accountBalance - withDrawalAmount; } public void depositAmount(int depositAmount) { accountBalance = accountBalance + depositAmount; } public int balanceEnquiry() { return accountBalance; } } |
Output :
Before withdraw Balance : 1000
After withdraw Balance : -1000
In this example, At Line :17 we declared int accountBalance = 1000; Here int data type does allows even negative values also. That is the reason why at Line:22 even when we try to deduct more amount than available balance JVM didn’t throw exception. Because this is not JVM rule violation.
Here we should not allow JVM to execute Line:22, That means we should terminate withDrawAmount() method. To terminate we have to throw an exception. Here we can throw any predefined exception. So in the above example we can re-write withDrawAmount() method as follows.
1 2 3 4 5 6 7 |
public void withDrawAmount(int withDrawalAmount) { // Here explicitly throwing predefined exception when business rule violated if (withDrawalAmount > accountBalance) { throw new IllegalArgumentException("Insufficient Funds!!!"); } accountBalance = accountBalance - withDrawalAmount; } |
In the above code snippet if withDrawalAmount > accountBalance then we are creating and throwing IllegalArgumentException. So that program will get terminated. Good thing here is we are not allowing the withdrawal when there are no sufficient funds in account.
But it is not recommended to use pre-defined exception for explaining ‘business failure’ scenario. It is always recommended to define custom exception for every business rule violation.
Here exactly we need the custom exception or user defined exception. We will rewrite above example with custom exception.
Example 02 : Create and throw user defined exception when business rule violated
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 |
package com.vidvaan.corejava.exception23.customexception; //We create and throw user defined exception when business rule violated public class Example02CustomException { public static void main(String[] args) { BankAccount2 obj = new BankAccount2(); System.out.println("Before withdraw Balance : " + obj.balanceEnquiry()); // limit termination of execution to withDrawAmount() instead terminating whole app try { obj.withDrawAmount(2000); } catch (InsufficientFundsException e) { System.out.println("Withdraw Failed : " + e.getMessage()); } System.out.println("After withdraw Balance : " + obj.balanceEnquiry()); } } class BankAccount2 { private int accountBalance = 1000; public void withDrawAmount(int withDrawalAmount) { // Here explicitly throwing user defined exception when business rule violated if (withDrawalAmount > accountBalance) { throw new InsufficientFundsException("Insufficient Funds!!!"); } accountBalance = accountBalance - withDrawalAmount; } public void depositAmount(int depositAmount) { accountBalance = accountBalance + depositAmount; } public int balanceEnquiry() { return accountBalance; } } // User defined exception for specific business rule violation class InsufficientFundsException extends RuntimeException { public InsufficientFundsException(String message) { super(message); } } |
Output :
Before withdraw Balance : 1000
Withdraw Failed : Insufficient Funds!!!
After withdraw Balance : 1000
In the above example, we defined user defined exception called ‘InsufficientFundsException’ to specify specific business rule violation. It is very clear and understandable exception.
Custom Exception
If we are creating exceptions as per our application need then we call them as ‘User defined exception’ or ‘Custom exception’. There are two types of custom exceptions.
- Checked custom exception
- Unchecked custom exception
Checked custom exception
Define a class by extending Exception class.
1 2 3 |
class CustomCheckedException extends Exception{ } |
Unchecked custom exception
Define a class by extending Runtime class.
1 2 3 |
class CustomUncheckedException extends RuntimeException{ } |
What is encapsulated in an Exception Object ?
The exception object contains name, description and Call Stack of the exception. When we print the exception all these(name, description and call stack) together will be printed, All these together called exception stack trace.
What code we have to write in Custom Exception class
In Custom Exception class we define constructors (which intern invokes super class constructors) to pass custom exception message and root cause exception details to super class constructors.
As Custom Exception class is child of Exception or RuntimeException, So by default Custom Exception class inherits all the exception methods. In general we don’t override any methods in Custom Exception class.
Example 03 – Custom Exception – encapsulate description
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 |
package com.vidvaan.corejava.exception23.customexception; //Custom Exception - encapsulate description public class Example03CustomExceptionEncapsulation { public static void main(String[] args) { System.out.println("main begin"); try { doStuff(); } catch (CustomExceptionDescriptionEncapsulation e) { e.printStackTrace(); } System.out.println("main end"); } public static void doStuff() { System.out.println("Something went wrong"); throw new CustomExceptionDescriptionEncapsulation("Something wrong !!!"); } } class CustomExceptionDescriptionEncapsulation extends RuntimeException { private static final long serialVersionUID = 1L; // No exception description public CustomExceptionDescriptionEncapsulation() { super(); } // exception description and previous root cause exception public CustomExceptionDescriptionEncapsulation(String message, Throwable cause) { super(message, cause); } // exception description public CustomExceptionDescriptionEncapsulation(String message) { super(message); } // previous root cause exception public CustomExceptionDescriptionEncapsulation(Throwable cause) { super(cause); } } |
Output :
main begin
Something went wrong
com.vidvaan.corejava.exception23.customexception.CustomExceptionDescriptionEncapsulation: Something wrong !!!
at com.vidvaan.corejava.exception23.customexception.Example03CustomExceptionEncapsulation.doStuff(Example03CustomExceptionEncapsulation.java:17)
at com.vidvaan.corejava.exception23.customexception.Example03CustomExceptionEncapsulation.main(Example03CustomExceptionEncapsulation.java:8)
main end
In the above example, in doStuff() method we are creating and throwing custom exception by encapsulating description into it. As we can see in the above output it print exception name, description and stack trace.
Example 04 – Custom Exception – encapsulate description and exception object
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 |
package com.vidvaan.corejava.exception23.customexception; import java.io.FileReader; import java.io.IOException; // Custom Exception - encapsulate description and exception object public class Example04CustomExceptionEncapsulation { public static void main(String[] args) { System.out.println("main begin"); try { readDataFromFile(); } catch (CustomException e) { e.printStackTrace(); } System.out.println("main end"); } public static void readDataFromFile() { try (FileReader fr = new FileReader("C:\\demo.txt")) { char[] a = new char[50]; fr.read(a); // reads file content to the array for (char c : a) System.out.print(c); // prints characters one by one } catch (IOException e) { throw new CustomException("Unable to read data from file !!!", e); } } } class CustomException extends RuntimeException { private static final long serialVersionUID = 1L; // No exception description public CustomException() { super(); } // exception description and previous root cause exception public CustomException(String message, Throwable cause) { super(message, cause); } // exception description public CustomException(String message) { super(message); } // previous root cause exception public CustomException(Throwable cause) { super(cause); } } |
Output :
main begin
com.vidvaan.corejava.exception23.customexception.CustomException: Unable to read data from file !!!
at com.vidvaan.corejava.exception23.customexception.Example04CustomExceptionEncapsulation.readDataFromFile(Example04CustomExceptionEncapsulation.java:25)
at com.vidvaan.corejava.exception23.customexception.Example04CustomExceptionEncapsulation.main(Example04CustomExceptionEncapsulation.java:11)
Caused by: java.io.FileNotFoundException: C:\demo.txt (The system cannot find the file specified)
at java.base/java.io.FileInputStream.open0(Native Method)
at java.base/java.io.FileInputStream.open(FileInputStream.java:212)
at java.base/java.io.FileInputStream.(FileInputStream.java:154)
at java.base/java.io.FileInputStream.(FileInputStream.java:123)
at java.base/java.io.FileReader.(FileReader.java:60)
at com.vidvaan.corejava.exception23.customexception.Example04CustomExceptionEncapsulation.readDataFromFile(Example04CustomExceptionEncapsulation.java:19)
… 1 more
main end
In the above example, We are catching IOException and creating custom exception by encapsulating description and IOException object. The Great thing here is when exception is printed it will print custom exception stack trace and IOException stack trace too. So we are not loosing the root cause of the problem.
When ever we are creating custom exception, we should always encapsulate root cause exception into custom exception (if there is any root cause exception) so that we don’t loose root cause exception stack trace
In Custom exception class, if we want we can write our own variables and methods to pass some status along with the exception object as per business needs.