I was surprised to notice that there wasn't a single tutorial on this forum which covered the creation of your own Exception classes, so I decided to make a small tutorial for you.
TL;DR - If you don't fancy reading all the details, you may jump straight to the code, as it's pretty self explanatory if you have a basic concept of Exceptions and their hierarchy.
Introduction
You might be surprised to know that creating your own Exception is as easy, if not easier than any other occasion where you would extend a super class.
The steps are simple:
1.) Realise a situation where creating your own Exception would be a benefit
2.) Name your Class the name you want your Exception to be known as
3.) Make a decision on what Exception to subclass, checked or unchecked (explained below)
4.) Throw it!
As some, but not all of you may know; there are two types of Exceptions, checked and unchecked where (in short) the differences between the two are that the compiler forces the programmer to handle checked Exceptions, but not unchecked Exceptions.
Anything which is known as unchecked comes under RuntimeException, and its subclasses.
Here are a few of the most common Runtime exceptions (See API for full list):
ArithmeticException, ClassCastException, IllegalArgumentException, NoSuchElementException, NullPointerException
All other subclasses of the Exception class (inclusive) are checked and you are therefore forced by the compiler to handle them.
A common example of this scenario is where the compiler forces you to handle IOException when dealing with files and the like.
In these cases, the programmer has two choices, either to pass the buck and throw the Exception, or to catch it in a try-catch block.
Now with this in mind, you need need to make the decision of what type of Exception you want to create, either one the user has to handle, or one which will inevitably stall the program.
To aid with that decision, it is always good to study current Exceptions and take into consideration their uses;
E.g. If NullPointerException was a checked Exception, programmers would need to flood their code with try catches for every scenario where there was a possibility of a method returning null,
which in all honesty, would be a massive and ugly pain, so It makes sense for it to be an unchecked Exception instead.
But for IOException, it makes more sense to force the programmer handle events such as 'File not found' so that program offers an alternative means of proceeding to the user, or to simple terminate gracefully instead of allowing the programmer to let the program stall.
Examples in code
Consider this system login program which prompts the user for a username, password and validates those inputs against the stored credentials.
The flow of execution will go one of two ways, either it will tell the user how they've successfully logged in, or it will throw an SecurityBreachException which when caught, will tell the user the login process failed. This is very basic example but will do nicely for demonstration purposes.
Example #1 - Checked.
public class SecurityBreachException extends Exception{
public SecurityBreachException(){
super("Attempted Security Breach - User attempted to breach security");
}
}
As the SecurityBreachException class extends Exception, it means this will be a checked exception, and will therefore need to be handled by the programmer.
Using its super class constructor (Exception(String message)), I set the message for my exception to "Attempted Security Breach - User attempted to breach security" which is the message that will now be registered with the thrown exception.
Although I do it here, I wouldn't exactly recommend you always extend the broad classes, such as Exception for checked and RuntimeException for unchecked, but extend relevant exceptions, such as IOException for anything media related, or in this case I probably should have extended IllegalAccessException - as in any case; specifics are best.
Now lets have a look at it in action.
import java.util.Scanner;
public class SecurityLogin {
//Only valid credentials [0] = username, [1] = password
private static String[] admin = { "jpf", "admin" };
private SecurityLogin() {
}
/**
* Validates whether user input matches stored credentials
* @param user User's attempt at 'Username'
* @param pass User's attempt at 'Password'
* @throws SecurityBreachException Thrown when details don't match
*/
public static void validateLogin(String user, String pass) throws SecurityBreachException {
if (user.equals(admin[0]) && pass.equals(admin[1])) {
System.out.printf("Welcome %s, you are now successfully logged in.", user); //match
} else {
throw new SecurityBreachException(); //No match, throw exception
}
}
public static void main(String... args) {
Scanner input = new Scanner(System.in);
System.out.println("Enter a Username: ");
String iUser = input.next();
System.out.println("Enter a Password: ");
String iPass = input.next();
try {
SecurityLogin.validateLogin(iUser, iPass);
} catch (SecurityBreachException e) {
System.out.println("Login Failed!");
}
}
}
What you need to realise here is that, when dealing with checked exceptions, every time you decide to throw an exception, you need to declare that the method from which it is called throws that exception too, be it a 'normal' method or the main method. To do this, you need to use the throws clause, e.g. "void myMethod() throws BoomerangException".
Within the main method, I catch any SecurityBreachException's within a try-catch block, and print a "Login Failed" message on failure, but I could have just set the main method to also throw the exception, but that would have simply led to the program stalling once it occurred.
If you were to execute the program with correct inputs, the output on the command line would look like the following:
Enter a Username:
jpf
Enter a Password:
admin
Welcome jpf, you are now successfully logged in.
If it was executed with incorrect inputs however, the outcome would be as follows:
Enter a Username:
Hax0RZ
Enter a Password:
admin
Login Failed!
If you were to replace
System.out.println("Login Failed!") with System.out.println(e.getMessage()) however,
it would print out "Attempted Security Breach - User attempted to breach security" (The message registered with the exception).
Now you've seen an example of a checked exception, lets take a look at an unchecked implementation.
Example #2 - Unchecked
public class SecurityBreachException extends IllegalArgumentException{
public SecurityBreachException(){
super("Attempted Security Breach - User attempted to breach security");
}
}
Notice how I've replaced 'extends Exception' with 'extends IllegalArgumentException';
there are two reasons for this, mainly because it is a subclass of RuntimeException, meaning unchecked
and secondly, because it is more specific that simply extending RuntimeException. Everything else in the class is left untouched, meaning it is a very simple transition.
Here is the new implementation, in which you should notice a few differences.
import java.util.Scanner;
public class SecurityLogin {
// Only valid credentials [0] = username, [1] = password
private static String[] admin = { "jpf", "admin" };
private SecurityLogin() {
}
/**
* Validates whether user input matches stored credentials
* @param user User's attempt at 'Username'
* @param pass User's attempt at 'Password'
*/
public static void validateLogin(String user, String pass) {
if (user.equals(admin[0]) && pass.equals(admin[1])) {
System.out.printf("Welcome %s, you are now successfully logged in.", user); // match
} else {
throw new SecurityBreachException(); // No match, throw exception
}
}
public static void main(String... args) {
Scanner input = new Scanner(System.in);
System.out.println("Enter a Username: ");
String iUser = input.next();
System.out.println("Enter a Password: ");
String iPass = input.next();
SecurityLogin.validateLogin(iUser, iPass);
}
}
I'm sure you noticed quite a few things which looked different this time around, mainly how much tidier the code looks as much as anything else.
This is because there is no mention of clauses such as try-catch or throws, and unlike the previous example this code is correct and compilable without a try-catch block as the compiler doesn't force you to handle anything.
However, unchecked doesn't mean uncheckable, as you could always surround the code with a try-catch block and handle any exceptions which might arise, but if that would be a common approach for the problem, it might be better reconsider your approach and possibly have the Exception be checked instead of unchecked.
With this approach - if the login is successful, the user will receive the same success message as in the previous example, but if the inputs do not match, the program will stall and spit out the following message:
Exception in thread "main" SecurityBreachException: Attempted Security Breach - User attempted to breach security
at SecurityLogin.validateLogin(SecurityLogin.java:20)
at SecurityLogin.main(SecurityLogin.java:33)
Which isn't the prettiest of sights
And well... that's it - thanks for reading and I hope you learnt something.
Feel free to ask more questions & the like.