A - Explanation
SQLException
is a subtype of java.lang.Exception
and also it is implementing the Iterable<Throwable>
class. Programmers prefer throwing different subtypes of Exception
class because on some higher level, they want to catch the exact sub-Exception class so that they can be sure that that specific Exception is thrown on some exact scenario. Thus, they can know the exact source of Exception.
B - Example
Consider you have written a method that throws multiple exceptions. Let's say, you take a json String and parse it, then persist it on the database. Consider the following method;
public boolean persistCustomer(String jsonString) throws SQLException, IOException {
Connection conn = getConnection();
PreparedStatement preparedStatement = null;
ObjectMapper objectMapper = new ObjectMapper();
try {
Customer customer = objectMapper.readValue(jsonString, Customer.class);
preparedStatement = conn.prepareStatement(PERSIST_CUSTOMER);
preparedStatement.setString (1, customer.getName());
preparedStatement.setInt (2, customer.getAge());
preparedStatement.setBoolean (3, customer.getIsRegular());
preparedStatement.executeUpdate();
return true;
} catch (IOException e) {
throw e;
} finally {
try {
if (preparedStatement != null)
preparedStatement.close();
if (conn != null)
conn.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
}
In this method, we are converting JSON into a Customer class and also we persist customer class to the database.
The following lines throw SQLException
;
preparedStatement = conn.prepareStatement(PERSIST_CUSTOMER);
preparedStatement.setString (1, customer.getName());
preparedStatement.setInt (2, customer.getAge());
preparedStatement.setBoolean (3, customer.getIsRegular());
preparedStatement.executeUpdate();
prepareStatement()
, setters and executeUpdate()
methods, all of them throwing SQLException
's. But also, the line that which we convert JSON in a String into a Customer object, also throws several Exceptions other than SQLException.
Customer customer = objectMapper.readValue(jsonString, Customer.class);
readValue()
method throws JsonParseException
, JsonMappingException
and also IOException
. All of them can be catched using an IOException
because the JSON related exceptions extend IOException
.
I'm going to provide two different examples so that it will be obvious to understand why we need different types of Exceptions.
C - Bad Practice: Using Exception To Catch All Exceptions
public class BadPracticeExample {
public static void main(String[] args) {
MySQLUtil dbUtil = new MySQLUtil();
String jsonString = "{\"name\":\"Levent\",\"age\":31,\"isRegular\":true}";
try {
dbUtil.persistCustomer(jsonString);
} catch (Exception e) {
System.out.println("A problem occured");
}
}
}
As you can see, it catches the Exception but what are we going to do if we need special exception handling for two different sources of problems? persistCustomer can throw either IOException or an SQLException and what if we need to do different set of tasks to handle those problems? I want to send an email to the database admin when an SQLException occurs and I want to continue when a JSON parsing problem occurs, on the case that an IOException is catched?
In this scenario you can't do that. Here is the output of the code snippet above and we are only sure that an Exception occured but we don't have any idea about what is the source of it;
A problem occured
D - Good Practice Example I: SQL Exception catched
public class GoodPracticeExample {
public static void main(String[] args) {
MySQLUtil dbUtil = new MySQLUtil();
String jsonString = "{\"name\":\"Levent\",\"age\":31,\"isRegular\":true}";
try {
dbUtil.persistCustomer(jsonString);
} catch (SQLException e) {
System.out.println("SQL Exception catched, SQL State : " + e.getSQLState());
System.out.println("Error Code : " + e.getErrorCode());
System.out.println("Error Message : " + e.getMessage());
} catch (IOException e) {
System.out.println("Cannot parse JSON : " + jsonString);
System.out.println("Error Message : " + e.getMessage());
}
}
}
As you can see, we catch for both, JSON and SQL problem and in this example, submethod is trying to persist DB where there is no table. The output is as below;
SQL Exception catched, SQL State : 42000
Error Code : 1142
Error Message : INSERT command denied to user 'levent'@'example.com' for table 'CUSTOMER'
So we have catched SQL Exception and we have all parameters we need to send an alarm email. We can add additional handler or utility methods on the SQLException catch block.
D - Good Practice Example II: IOExceptoin catched on Parsing Error
public class GoodPracticeExample {
public static void main(String[] args) {
MySQLUtil dbUtil = new MySQLUtil();
String jsonString = "{\"Zname\":\"Levent\",\"age\":31,\"isRegular\":true}";
try {
dbUtil.persistCustomer(jsonString);
} catch (SQLException e) {
System.out.println("SQL Exception catched, SQL State : " + e.getSQLState());
System.out.println("Error Code : " + e.getErrorCode());
System.out.println("Error Message : " + e.getMessage());
} catch (IOException e) {
System.out.println("Cannot parse JSON : " + jsonString);
System.out.println("Error Message : " + e.getMessage());
}
}
}
If you've noticed, I"ve corrupted the JSON to cause an IOException. Now in the json string, instead of "name", "Zname" is written which will cause Jackson Parser to fail. Let's checkout the output of this code.
Cannot parse JSON : {"Zname":"Levent","age":31,"isRegular":true}
Error Message : Unrecognized field "Zname" (class com.divilioglu.db.utils$Customer), not marked as ignorable (3 known properties: "isRegular", "name", "age"])
at [Source: (String)"{"Zname":"Levent","age":31,"isRegular":true}"; line: 1, column: 11] (through reference chain: com.divilioglu.db.utils.MySQLUtil$Customer["Zname"])
As you can see, we catched the specific scenario and we are sure, this comes from the line in dbUtil.persistCustomer() method which can be seen below;
Customer customer = objectMapper.readValue(jsonString, Customer.class);
E - Conclusion
So as it is a best practice to create new Exceptions by extending existing Exception classes. While writing your code at first, you may think that it is an overkill and you won't need additional Exception classes, but you will need them when you need distinguish the source of the problem and handle them independently.
In this example demonstrated above, I can independently catch IOException
and SQLException
and the sources of both Exceptions are coming from the same method. I want to distinguish both so that I can handle them independently. You cannot have that flexibility if you just wrap all the Exceptions with the base Exception class.