Java Error Handling

What exceptions to handle, where to place the exceptions handlers, the correspondence of 'try's to 'catches', the nesting of exception handlers, and which methods will have the throw keywords, are design decisions and are addressed in the design activity of the programming process.

Throwing Exceptions

Identify Normal Flow

Every application has some type of process(s) that execute(s) to get work done. Within those processes, the application typically follows a certain path, every time it is runs. That path is called the normal application flow. Anything outside of that path(s) is an exception.


Identify Possible Exceptions

Identifying exceptions can be tricky business but it is a key element to defining software architecture. Once the application flow is determined, then dive into the exceptions. Most of the time, this is a process of discovery. I can usually identify a handful of exceptions at first and then end up adding additional ones as I understand more about the problem domain and the normal application flow. Exceptions also change with requirements. The key is to create an architecture within the application that is easy to extend when required and that goes for exceptions as well.

Definitions

  1. unchecked exceptions extend RuntimeException
  2. unchecked exceptions are those that require user intervention to fix
  3. unchecked exceptions propagate back to the client application as an error code and message
  4. checked exceptions extend Exception
  5. checked exceptions are addressed by the application by applying an alternative path: i.e. credit card processing
  6. checked exceptions do not have an error code
  7. checked exceptions are specific to an exceptional application behavior

Steps to follow when identifying exceptions

  1. identify the normal application (processing) flow
  2. at each point in the application flow, determine what can go wrong
  3. exceptions are behaviors that change the normal application flow but are expected
  4. applications need to expect both application process and system problems to occur and handle them appropriately: i.e. API timeout, database connection failure, other nasty things, etc.
  5. do not couple through exceptions when interfacing with other libraries and systems
  6. define exception classes best suited to describe the exception conditions based on the architecture
  7. create tests that cause the exception to occur and verify it is handled appropriately

Example Application

The best way to understand exceptions is through an example.

Objective: we need to create an application to manage the assembly and painting of a new car.

Tasks: high level process definition for creating the application

  1. Determine normal application flow
  2. Identify possible exceptions to this flow (what can happen that is outside the normal process, those are exceptions)
  3. Identify responsibility within the application (what objects are needed and what do they do)
  4. Define and build source code architecture with specific code modules (packages)
  5. Stub in objects and exception classes
  6. Create build scripts and tests for code modules
  7. Implement the application

Normal application flow:

  1. The person wanting to build the car inputs the car type and painter (any type works)
  2. Check that parts are in stock before car is assembled
  3. Assemble car by adding parts to the car
  4. Painted the car after it has been assembled
  5. The assembled, painted car is returned to the person

Possible exceptions:

  1. part out of stock - selected part is not in stock at time of assembly
  2. car not assembled before painting - the car must be assembled before painting
  3. invalid paint color - the selected car color is not valid
  4. painter not available - the painter has not been scheduled to paint the car
  5. unable to order part - there was a problem ordering the part

Exception business processes:

  1. part out of stock - application tries to resolve this problem by:
    1. ordering parts that are out of stock
    2. let user know there was an problem if an exception occurs while ordering parts
  2. car not assembled before painting - let user know the car must be assembled before painting
  3. invalid paint color - let user know they must select a different paint color before painting the assembled car
  4. painter not available - let user know that the painter was not available
  5. unable to order part - let user know that there was a problem ordering the part(s)

Application design decisions:

  1. assembler is given the parts list for car
  2. assembler builds car
  3. painter paints only assembled cars
  4. painter knows how to paint the car
  5. painter does not know how to assemble car
  6. assembler gives painter the car
  7. car does not know how to build or paint itself
  8. car blindly accepts parts from assembler
  9. keep car objects light
  10. car knows all it's properties: name, price, etc….
  11. assembler accepts car parts list based on car it wants to build
  12. parts are added to the car from the assembler
  13. control is given to the painter and assembler to manage individual process
  14. when the car color is set, the car is considered painted
  15. cannot paint cars black

Source code modules:

  1. main - application process
  2. assemble - assembles the car once parts are added
  3. car - car object that contains the parts and state (painted / not painted)
  4. order - order parts if they are not in stock
  5. paint - paints assembled cars
  6. parts - all the available parts that can be added to the car
  7. stock - verify the parts list is in stock

Creating Application Exception Classes
  1. there are three categories of exception: global application, module, and specific exceptions
  2. the global application exception extends the java RuntimeException (Fault Barrier)
  3. all module exceptions extend the global application exception
  4. the specific exceptions extend the java Exception class
  5. extending the java Exception class forces the exception to be handled (checked) while RuntimeException does not (unchecked)
  6. the naming convention for module exceptions is: [module name]Exception i.e. OrderException, PaintException, AssembleException, etc.
  7. the naming convention for specific exceptions is: [descriptive identifier]Exception i.e. CarNotAssembledException, MySpecificException, etc.

Exception Class Templates

category: global application exception
type: unchecked
extends: java RuntimeException
file: [Application]Exception.java i.e. CarApplicationException.java
error code: required
package: exception
class definition: public class [Application]Exception extends RuntimeException {
attributes:
private int _errorCode;
private String _errorMsg;
constructor:
public CarApplicationException(int errorCode,String errorMsg) {
super(errorMsg);
_errorCode = errorCode;
_errorMsg = errorMsg;
}
accessors:
public int getErrorCode() { return _errorCode; }
public String getErrorMsg() { return _errorMsg; }
category: module exception
type: unchecked
extends: [Application]Exception
file: [Module]Exception.java i.e. PartsException.java
error code: required
package [Module].exception;
class definition: public class [Module]Exception extends [Application]Exception
attributes: none
constructor:
public [Module]Exception(int errorCode,String errorMsg) {
super(errorCode,errorMsg);
}
accessors: none
category: specific exception
type: checked
extends: java Exception
file: [Description]Exception.java
error code: not required
package [Description].exception
class definition: public class InvalidPartsException extends Exception {
attributes: any data type i.e. list, map, string, etc.
constructor:
public [Description]Exception(String errorMsg){
super(errorMsg);
}
public [Description]Exception(){
super([Error file].[specific error message]);
}
others….
accessors: getters for attributes as required


Exception Packages

Each module has an exception package which contains exception files:

module = parts
file path = /src/parts/exception (module name is 'parts')
package = parts.exception
class files: PartsException.java, etc…;


Error Codes and Messages

  1. Every module has a properties file within package parts.exception which maps module level error codes to default error messages .
  2. error codes are integers (int)
  3. error messages are strings (String)
  4. At the application level a properties file reader will be created.For applications on top of ofbiz,this property reader file will/may wrap the ofbiz property utils class. One of the advantages of doing this will be of less IO performed for this file as ofbiz caches the file.
    • When the exception classes try to use this reader file ,they will provide the name of the error property file to read from .


Error Class Examples

module: parts
file: PartsErrorCode.properties
package parts.exception;


Calling Client Example

The calling client catches both the application exception and the specific out of stock exception. Most of the exceptions thrown in this application are RuntimeExceptions and will hit the fault barrier to be transformed (printed to the screen). In many client server applications, these error messages can be bundled in XML and returned to the caller. The type of exception handling model changes based on application requirements but from my experience most exceptions can be Runtime exceptions. The other factor determining exception type (checked vs. unchecked) are the business requirements. Keep in mind that lower levels usually don't know how to handle exceptions well anyway and end up eating them (not recommended) or bubbling them up to the next calling level. Bubbling up checked exceptions, that can't be handled, is a good practice, but Runtime exceptions reduce the amount of exception handling code required in the application. This keeps the logic clean especially when most of the exception handling code is not adding value.

static public void main( String args[] ){
System.out.println("«— car builder application —»");
String type = (String)args[0]; // car type
String name = (String)args[1]; // painter name
System.out.println("«— building car of type: " + type + " »");
System.out.println("« selected painter is: " + name + " —»");
try {
PartsList partsList = PartsList.loadParts(type);
Stock stock = new Stock(partsList);
try {
stock.check();
} catch(OutOfStockException e) { //checked exception
Order order = new Order(e.getOutOfStockParts());
order.process();
}
Assemble assembler = new Assemble(partsList,type);
Car car = assembler.assemble();
Painter painter = new Painter(name,car,new Color("green"));
Scheduler schedule = new Scheduler(painter);
schedule.schedule();
painter.paint();
} catch (CarApplicationException e) { // fault barrier: return everything else to the user to handle
System.out.println("Processing error: [" + e.getErrorCode() + "] " + e.getErrorMsg());
System.out.println("Exception class: " + e.getClass());
}
return;
}


Catching Exceptions

Rules for catching exceptions:

  1. do not stop the propagation of exceptions within a piece of code that can not handle the exception.
  2. catch 3rd party exceptions when they occur, capture exception information, load application exception and then throw application exceptions to caller.

Source: The Java World, http://thejavaworld.wikidot.com/java-error-handling
Creative Commons License This work is licensed under a Creative Commons Attribution-ShareAlike 3.0 License.

Last modified: Thursday, August 17, 2023, 5:36 AM