Unchecked exceptions:
represent defects in the program (bugs) – often invalid arguments passed to a non-private method. To quote from The Java Programming Language, by Gosling, Arnold, and Holmes: “Unchecked runtime exceptions represent conditions that, generally speaking, reflect errors in your program’s logic and cannot be reasonably recovered from at run time.”
are subclasses of RuntimeException, and are usually implemented using IllegalArgumentException, NullPointerException, or IllegalStateException
a method is not obliged to establish a policy for the unchecked exceptions thrown by its implementation (and they almost always do not do so)
Checked exceptions:
represent invalid conditions in areas outside the immediate control of the program (invalid user input, database problems, network outages, absent files)
are subclasses of Exception
a method is obliged to establish a policy for all checked exceptions thrown by its implementation (either pass the checked exception further up the stack, or handle it somehow)
It’s somewhat confusing, but note as well that RuntimeException (unchecked) is itself a subclass of Exception (checked).
Example 1
Model Objects are the data-centric classes used to represent items in a particular domain. Model Object constructors need to handle both arbitrary user input, and input from underlying database ResultSets.
Model Object constructors should throw checked exceptions:
the program may have no direct control over user input. This is particularly true in web applications. It seems safest for a Model Object to treat user input as having arbitrary, unvalidated content.
it’s not safe for an application to make any assumptions about the state of the database. The database is an independent entity, and its data may be changed by various means, outside of any particular application. For example, data load tools are commonly used to create an initial state for a new database. Such data can easily violate the constraints built into a calling application. Thus, the safest assumption is to treat database ResultSets as having arbitrary, unvalidated content.
Here is an example Model Object, taken from the WEB4J example application. Its constructor throws ModelCtorException (a checked exception) :
package hirondelle.fish.main.resto; import hirondelle.web4j.model.ModelCtorException; import hirondelle.web4j.model.ModelUtil; import hirondelle.web4j.model.Id; import hirondelle.web4j.security.SafeText; import hirondelle.web4j.model.Decimal; import static hirondelle.web4j.model.Decimal.ZERO; import hirondelle.web4j.model.Check; import hirondelle.web4j.model.Validator; import static hirondelle.web4j.util.Consts.FAILS; /** Model Object for a Restaurant. */ public final class Resto { /** Full constructor. @param aId underlying database internal identifier (optional) 1..50 characters @param aName of the restaurant (required), 2..50 characters @param aLocation street address of the restaurant (optional), 2..50 characters @param aPrice of the fish and chips meal (optional) $0.00..$100.00 @param aComment on the restaurant in general (optional) 2..50 characters */ public Resto( Id aId, SafeText aName, SafeText aLocation, Decimal aPrice, SafeText aComment ) throws ModelCtorException { fId = aId; fName = aName; fLocation = aLocation; fPrice = aPrice; fComment = aComment; validateState(); } public Id getId() { return fId; } public SafeText getName() { return fName; } public SafeText getLocation() { return fLocation; } public Decimal getPrice() { return fPrice; } public SafeText getComment() { return fComment; } @Override public String toString(){ return ModelUtil.toStringFor(this); } @Override public boolean equals(Object aThat){ Boolean result = ModelUtil.quickEquals(this, aThat); if (result == null) { Resto that = (Resto) aThat; result = ModelUtil.equalsFor( this.getSignificantFields(), that.getSignificantFields() ); } return result; } @Override public int hashCode(){ if (fHashCode == 0){ fHashCode = ModelUtil.hashCodeFor(getSignificantFields()); } return fHashCode; } // PRIVATE private final Id fId; private final SafeText fName; private final SafeText fLocation; private final Decimal fPrice; private final SafeText fComment; private int fHashCode; private static final Decimal HUNDRED = Decimal.from("100"); private void validateState() throws ModelCtorException { ModelCtorException ex = new ModelCtorException(); if (FAILS == Check.optional(fId, Check.range(1,50))) { ex.add("Id is optional, 1..50 chars."); } if (FAILS == Check.required(fName, Check.range(2,50))) { ex.add("Restaurant Name is required, 2..50 chars."); } if (FAILS == Check.optional(fLocation, Check.range(2,50))) { ex.add("Location is optional, 2..50 chars."); } Validator[] priceChecks = {Check.range(ZERO, HUNDRED), Check.numDecimalsAlways(2)}; if (FAILS == Check.optional(fPrice, priceChecks)) { ex.add("Price is optional, 0.00 to 100.00."); } if (FAILS == Check.optional(fComment, Check.range(2,50))) { ex.add("Comment is optional, 2..50 chars."); } if ( ! ex.isEmpty() ) throw ex; } private Object[] getSignificantFields(){ return new Object[] {fName, fLocation, fPrice, fComment}; } }
Example 2
Args is a convenient utility class. It performs common validations on method arguments. If a validation fails, then it throws an unchecked exception. It is suitable for checking the internal consistency of program, but not for checking arbitrary user input.
package hirondelle.web4j.util; import java.util.regex.*; /** Utility methods for common argument validations. Replaces if statements at the start of a method with more compact method calls. Example use case. Instead of : public void doThis(String aText){ if (!Util.textHasContent(aText)){ throw new IllegalArgumentException(); } //..main body elided } One may instead write : public void doThis(String aText){ Args.checkForContent(aText); //..main body elided } */ public final class Args { /** IfaText
does not satisfy {@link Util#textHasContent}, then throw anIllegalArgumentException
. Most text used in an application is meaningful only if it has visible content. */ public static void checkForContent(String aText){ if( ! Util.textHasContent(aText) ){ throw new IllegalArgumentException("Text has no visible content"); } } /** If {@link Util#isInRange} returnsfalse
, then throw anIllegalArgumentException
. @param aLow is less than or equal toaHigh
. */ public static void checkForRange(int aNumber, int aLow, int aHigh) { if ( ! Util.isInRange(aNumber, aLow, aHigh) ) { throw new IllegalArgumentException(aNumber + " not in range " + aLow + ".." + aHigh); } } /** If aNumber is less than 1, then throw an IllegalArgumentException. */ public static void checkForPositive(int aNumber) { if (aNumber < 1) { throw new IllegalArgumentException(aNumber + " is less than 1"); } } /** If {@link Util#matches} returns false, then throw anIllegalArgumentException
. */ public static void checkForMatch(Pattern aPattern, String aText){ if (! Util.matches(aPattern, aText)){ throw new IllegalArgumentException( "Text " + Util.quote(aText) + " does not match '" +aPattern.pattern()+ "'" ); } } /** IfaObject
is null, then throw aNullPointerException
. Use cases : doSomething( Football aBall ){ //1. call some method on the argument : //if aBall is null, then exception is automatically thrown, so //there is no need for an explicit check for null. aBall.inflate(); //2. assign to a corresponding field (common in constructors): //if aBall is null, no exception is immediately thrown, so //an explicit check for null may be useful here Args.checkForNull( aBall ); fBall = aBall; //3. pass on to some other method as parameter : //it may or may not be appropriate to have an explicit check //for null here, according the needs of the problem Args.checkForNull( aBall ); //?? fReferee.verify( aBall ); } */ public static void checkForNull(Object aObject) { if (aObject == null) { throw new NullPointerException(); } } // PRIVATE private Args(){ //empty - prevent construction } }