Every Java developer naturally encounters situations in her or his programming life, where the question arises: Why do I have to deal with this checked exception? Wouldn’t it it much less work, if that exception had been designed as a runtime exception?
Creating a String
from a byte
array with the default and always supported character encoding UTF-8 is one example:
try { ... = new String(myByteArray, "UTF-8"); } catch(final UnsupportedEncodingException e) { throw new IllegalStateException("Will never happen.", e); }
Dealing with URLs may also be sometimes annoying:
try { final String iKnowThisIsAValidUrl = ...; final URL url = new URL(iKnowThisIsAValidUrl); ... } catch (final MalformedURLException e) { ... }
Exceptions are useful. They detach the location, where the problem has been encountered, from the location, where it is handled. But shouldn’t we just use runtime exceptions, since checked exceptions are just annoying?
Many authors consider checked exceptions as a design failure (e.g. Bruce Eckel and Rod Waldhoff) or discuss their use (e.g. Rod Johnson, Neal Gafter, and – very interesting read: Brian Goetz).
What makes checked exceptions useful?
Catch what you cannot see
If an API provides a method without documentation and without declaring runtime exceptions, developers can hardly determine, how this interface will react, when leaving the happy path. Even a source code analysis may not be helpful, since runtime exceptions can bubble up from anywhere underneath. You cannot catch what you cannot see.
Even if the runtime exceptions are declared and/or documented, the compiler and IDE may not help you much using the API. If you are using a method that declares a checked exception, your development environment will urge you to deal with it.
Thus, checked exceptions can make API use easier, since they make the intended usage explicit: If the check is useful to be noticed and handled by the caller.
But what are good rules to design exception handling for a library or application?
Faults and Contingency Exceptions
In his article Effective Java Exceptions Barry Ruzek advises to distinguishes between
- Fault
- Contingency
in an exception design.
Where faults basically are used to signal problems that usually cannot be handled by the client (such as database connection or IO problems), contingency exceptions model business problems (like insufficient funds or overdraft exceptions). Barry Ruzek summarizes
Condition | Contingency | Fault |
---|---|---|
Is considered to be | A part of the design | A nasty surprise |
Is expected to happen | Regularly but rarely | Never |
Who cares about it | The upstream code that invokes the method | The people who need to fix the problem |
Examples | Alternative return modes | Programming bugs, hardware malfunctions, configuration mistakes, missing files, unavailable servers |
Best Mapping | A checked exception | An unchecked exception |
Source: Mapping Java Exceptions, Barry Ruzek
Sometimes exceptions are thrown because preconditions are not met. This is caused by passing invalid arguments to the method call. This is usually a kind of programming error and therefore something to be handled as a fault. It may also be a form of validation problem. If this call is initiate by the system, this is a system problem (e.g. inconsistent data) and usually a fault. If the call is initiated by a user input, this is defined as a contingency. The user is informed of the validation problems and may give it another try.
Technical and Business Exceptions
Dan Bergh Johnsson defines technical and business exceptions in his article Distinguish Business Exceptions from Technical:
Term | Definition |
---|---|
Technical Exception | Problem that prevents us from using the application. |
Business Exception | Problem that we get informed of and therefore prevents us of misusing the application. |
This definition matches that of faults and contingencies. A technical exception is a fault, a business exception a contingency. I favor the terms fault and contingency, because I think it is too easy to get distracted by the term technical, where someone might think of IOException
or SQLException
and therefore associates it with the resource layer. The term business is easily associated with the business layer only. While this is a clear misinterpretation not intended by the author, the term fault is neutral. A fault, a defect that can cause an error, may occur on any layer. A contingency either.
My Contingency is your Fault
Is it a failure to have e.g. IOException
s declared as checked exceptions? I think not. From a libraries perspective an attempt to read from a stream that fails, is a contingency the client code has to deal with. Having the compiler check that is really a good idea, since this problem cannot be overlooked.
Is it a good idea to let the IOException
bubble up the call line within an application? I think not. The contingency has to be handled inside the library. If this is not possible, the API designer may want to map the contingency exception to an unchecked or checked exception. Within an application, one would usually see the problem as a fault and therefore propagate it as an unchecked exception. All intermediaries are now free to handle it or not. The application may deal with it in the presentation layer, where the problem is displayed to the client.
Context Information
Exception chaining in this situation is usually a good thing. It allows you to add context information to the caught exception that is not available at lower layers (Contexts and Dependency Injection may give an alternative solution to this problem). It is somewhat laborious:
try { ... } catch (final OtherException e) { throw new MyException(e, some, additional, info); }
But it makes the problem clear for the people who have to analyze it. If OtherException
is a checked exception, adding context information cannot be overlooked. If it is an unchecked exception it may. In an application, where all faults are rooted in a single unchecked MyFaultException
, you may deal with these problems where it best fits, without needing to declare checked exceptions on every method. If there is context you may want to propagate you can always use:
try { ... } catch (final MyFaultException e) { throw new MySpecificFaultException(e, some, additional, info); }
Plenty of declared Exceptions?
In theory there can be plenty of exceptions occur within a component. Each of them has to be documented as part of the API. Having them all rooted in one parent exceptions makes the catching easier, but they still have to be declared individually. As long as dealing with them, checked or unchecked, makes sense from the client’s point of view.
void checkPortfolio(Customer customer, CompanyShare share) throws NullPointerException, DatabaseException, SecurityException, ... ShareNotPartOfPortfolioException { ... }
In practice this is not a great deal. If it is a fault, its a runtime exception which may be declared by its parent exception type. If a method is doing just one thing, there is often just a few exception types that are relevant to the client.
void checkPortfolio(Customer customer, CompanyShare share) throws NullPointerException, FaultException, ShareNotPartOfPortfolioException { ... }
The client is usually not interested in the actual fault. Knowing that a fault has been encountered is all that is relevant to the client. Therefore the client deals with the FaultException
only. Declaring and documenting it allows developers to keep possible problems in mind to decide to deal with it or not.
Conclusion
Runtime and checked exceptions have both their advantages, serving different requirements. Faults and contingency exceptions should be considered in designing exception handling. These concepts can be easily applied to smartics Exceptions. You may have a look at Best Practices Designing Exceptions or read the article What is smartics-exceptions all about? for an introduction to designing exceptions with smartics Exceptions.
The Java library smartics exception has been released with version 0.9.0.
This library allows to design exceptions in your application and to generate reports on the error codes. The exceptioncodes-maven-plugin generates exception codes reports.
For details on this library please visit the project's homepage, for changes since the last version, please consult the release report.
We recently posted that version 0.7.0 of smartics-exceptions has been released. This article motivates the ideas behind smartics Exceptions and introduces its features briefly. You may find evidence to have a closer look at this library. Hopefully …
We will first show some traditional steps in designing an exception and then show what it feels like to do it with the smartics-exceptions library. If you feel to skip the first part, jump to 64127427.
Developing an Exception in Java
Imaging you want to signal that a component has not been initialized successfully. You may write something like this:
throw new IllegalStateException();
This is quick and fast for the developer. To help our team members in the ops we opt to take some more time:
throw new IllegalStateException( String.format("The component '%s'" + " has not been initialized properly.", component));
Well, to be honest, we have some context here, we might want to add, since ops is our friend:
throw new IllegalStateException( String.format("The component '%s'" + " has not been initialized properly." + "This is because ... [some details follow]." + "The consequences are that ... [some consequences follow]." + "To fix this [some advices to take actions follow]." + "For further information," + " please refer to" + " http://somehost//app/components/a/not-initialized.html", component, some, further, context, info));
Ok, yes, I’m kidding. A developer won’t write all this exception message in the Java source. S/he would create a message bundle and put the text resources there.
Maybe someone wants to catch this exception up the stream. We should create an exception type:
public class NotInitializedException extends FaultException { // we have fields for: component, some, further, context, and info // we construct our message in the constructor ... public NotInitializedException(...) { super(String.format("The component '%s'" + " has not been initialized properly." + "This is because ... [some details follow]." + "The consequences are that ... [some consequences follow]." + "To fix this [some advices to take actions follow]." + "For further information," + " please refer to" + " http://somehost//app/components/a/not-initialized.html", component, some, further, context, info))); this.component = component; ... } }
And in return simplified our throwing action:
throw new NotInitializedException( component, some, further, context, info);
Then one might think: “I also want those fancy exception codes, I’ve seen by libraries such as Weld!”
WELD-001408 Unsatisfied dependencies for type ...
Therefore:
public class NotInitializedException extends FaultException { // we have fields for: component, some, further, context, and info // we construct our message in the constructor ... public NotInitializedException (...) { super(String.format("MYCOMP-0001: The component '%s'" + ... [all the stuff from above] component, some, further, context, info))); this.component = component; ... } }
Ok, the code should be a constant. So it is more visible to my fellow team members:
public class Codes { public static final String MYCOMP_0001 = "MYCOMP-0001"; ... } import static ......Codes.MYCOMP_0001; public class NotInitializedException extends FaultException { // we have fields for: component, some, further, context, and info // we construct our message in the constructor ... public NotInitializedException (...) { super(String.format(MYCOMP_0001 + ": The component '%s'" + ... [all the stuff from above] component, some, further, context, info))); this.component = component; ... } }
Well, this should be documented somewhere. Set a reminder:
public class NotInitializedException extends FaultException { // we have fields for: component, some, further, context, and info // we construct our message in the constructor ... public NotInitializedException (...) { // FIXME: Add code to the manual and add some documentation!!!! super(String.format(MYCOMP_0001 + ": The component '%s'" + ... [all the stuff from above] component, some, further, context, info))); this.component = component; ... } }
Developing an Exception with smartics-exceptions
Let’s start with the exception code. This time we use an enumeration (may already exist in which case we just add the new code):
public enum ComponentCode ... { INITIALIZATION(1), ... }
And then the message bundle (which we place per naming convention in the same package as the code with the name ComponentCodeBundle.properties
– smartics-exceptions does the rest):
1.title=Initialization Problem 1=The component ''{0}'' has not been initialized properly. 1.details=This is because ... [some details follow]. 1.implications=The consequences are that ... \ [some consequences follow]. 1.todo=To fix this [some advices to take actions follow]. 1.url=http://somehost//app/components/a/not-initialized.html
Again, we define an exception class:
public class NotInitializedException extends FaultException { @MessageParam("0:name") private final Component component; public NotInitializedException(...) { super(..., ComponentCode.INITIALIZATION); this.component = component; ... } ... }
And we are finally ready to throw the exception:
throw new NotInitializedException( component, some, further, context, info);
Done.
Reports for free
And what we get for free is this (just an example, showing a couple of codes for three components):
And an directory full of XML documents containing the information on all exception codes to be served as input to the generation process of the handbook for ops (again just an example of one code):
<?xml version="1.0"?> <appcode> <id>Test/My-1000</id> <type>error</type> <component>Test/My</component> <implementation> <class>de.smartics.exceptions.sample.MyCode</class> <field>ZERO</field> </implementation> <short-description>The zero code</short-description> <package-description>Test codes.</package-description> <description>The zero code.</description> <messages> <message> <name>Title</name> <text>Test Exception Zero</text> </message> <message> <name>Summary</name> <text>The name is set to {name}. URL is '{url:host}:{url:port}': {cause:message}</text> <placeholders> <placeholder> <name>cause:message</name> <description>The root cause of the exception.</description> </placeholder> <placeholder> <name>name</name> <description>The name of the test exception.</description> </placeholder> <placeholder> <name>url:host</name> <description>The test URL.</description> </placeholder> <placeholder> <name>url:port</name> <description>The test URL.</description> </placeholder> </placeholders> </message> <message> <name>Details</name> <text>No details for the test.</text> </message> </messages> </appcode>
Conclusion
We do not intend to suggest that for every exception we want to raise, we should define an exception type. Sometimes throwing an existing exception with a message text is just enough:
throw new IllegalArgumentException("The value of ... is invalid.");
But if you have to design new exception classes and there are requirements for exception codes and reporting (and some other stuff), you may want to check out the smartics Exceptions library for Java.
Enjoy!
The Java library smartics exception has been released with version 0.7.0.
This library allows to design exceptions in your application and to generate reports on the error codes. The exceptioncodes-maven-plugin generates exception codes reports.
For details on this library please visit the project's homepage, for changes since the last version, please consult the release report.
The taglets library provides a couple of Javadoc tags that are rendered as an eye catcher in Javadoc API reports.
With JDK 1.7 the CSS styles for the generated API report have changed. Since the taglets library provides its own stylesheet and this stylesheet is based on Javadoc report structures prior to 1.7, the generation is now broken: The rendering is missing the correct stylesheet to layout propertly. Unfortunately there is no current release of the taglets library that fixes this issue.
But the workaround is quite easy. The taglets library relies on the following styles:
dt.tagletsTodo, dt.tagletsImpl , dt.tagletsEquivalence , dt.tagletsNote , dt.tagletsInfo, dt.tagletsDone , dt.tagletsWarning , dt.tagletsError { background-repeat: no-repeat; background-position: 0 center; padding-left: 19px; font-weight: bold; } dt.tagletsTodo b { background: #ff9 ; font-weight: bold; } dt.tagletsTodo { background-image: url(resources/icon_note_small.gif); margin-top: 1em ; } dd.tagletsTodo { margin-bottom: 1em; } dt.tagletsNote { background-image: url(resources/icon_note_small.gif); } dt.tagletsInfo { background-image: url(resources/icon_info_small.gif); } dt.tagletsDone { background-image: url(resources/icon_done_small.gif); } dt.tagletsWarning { background-image: url(resources/icon_warning_small.gif); } dt.tagletsError { background-image: url(resources/icon_error_small.gif); } dt.tagletsImpl { background-image: url(resources/icon_code_small.gif); margin-top: 1em ; } dd.tagletsImpl { margin-bottom: 1em; } dt.tagletsEquivalence { background-image: url(resources/icon_equivalence_small.gif); } dd.tagletsEquivalence { border-color: #888; border-style: dotted; border-width: 1px; } .tagletsEquivalence pre { padding: 0.33em ; margin: 0 ; } .tagletsTestCase pre { padding: 0 ; margin: 0 ; } .tagletsSource { border-color: #888; border-style: dotted; border-width: 1px; margin: .67em 0; padding: 0.33em ; } .tagletsSource p { margin-top: 0 ; margin-bottom: .33em; } .tagletsSource pre { margin: 0 ; padding: 0 ; } .tagletsStickyInfo, .tagletsStickyNote, .tagletsStickyWarning , .tagletsStickyError, .tagletsStickyDone, .tagletsExample { border-top: 5px solid #900; border-left: 1px solid #900 ; background-repeat: no-repeat; background-position: 5px 1.33em; background-color: #fff; margin: .67em 0; padding-left: 42px; min-height: 64px ; } .tagletsStickyInfo p, .tagletsStickyNote p, .tagletsStickyWarning p, .tagletsStickyError p, .tagletsStickyDone p, .tagletsExample p { margin-top: .33em ; margin-bottom: .33em; } .tagletsExample { background-image: url(resources/icon_code_large.gif); border-color: #888; } .tagletsStickyNote { background-image: url(resources/icon_note_large.gif); border-color: #c1a040; } .tagletsStickyWarning { background-image: url(resources/icon_warning_large.gif); border-color: #c60; } .tagletsStickyError { background-image: url(resources/icon_error_large.gif); border-color: #900; } .tagletsStickyDone { background-image: url(resources/icon_done_large.gif); border-color: #090; } .tagletsStickyInfo { background-image: url(resources/icon_info_large.gif); border-color: #069; } table.section { border-width: 1px; border-spacing: 2px; border-style: outset; border-color: black; border-collapse: collapse; } table.section th { border-width: 1px; padding: 3px; border-style: inset; border-color: black; } table.section td { border-width: 1px; padding: 3px; border-style: inset; border-color: black; } table.doc { border: none; } .doc th { background: #bbb; border: none; text-align: left; vertical-align: top; padding-left: 4px; padding-right: 4px; } .doc td { border: none; border-width: 1px; vertical-align: top; padding-left: 4px; padding-right: 4px; } .doca td { background: #ddd; border: none; border-width: 1px; vertical-align: top; padding-left: 4px; padding-right: 4px; } .docb td { background: #efefef; border: none; border-width: 1px; vertical-align: top; padding-left: 4px; padding-right: 4px; } /* --- Pretty Printing Styles ----------------------------------------------- */ .str { color: #080; } .kwd { color: #008; } .com { color: #800; } .typ { color: #606; } .lit { color: #066; } .pun { color: #660; } .pln { color: #000; } .tag { color: #008; } .atn { color: #606; } .atv { color: #080; } .dec { color: #606; } @media print { .str { color: #060; } .kwd { color: #006; font-weight: bold; } .com { color: #600; font-style: italic; } .typ { color: #404; font-weight: bold; } .lit { color: #044; } .pun { color: #440; } .pln { color: #000; } .tag { color: #006; font-weight: bold; } .atn { color: #404; } .atv { color: #060; } }
If you add these styles to your stylesheet, the stylesheet can be integrated into your Maven build process like this:
<additionalJOption> -J-DTaglets.shutdown.javadoc-stylesheet-copy.files=${basedir}/src/etc/stylesheet.css </additionalJOption>
Add this property to the configuration of the Maven Javadoc Plugin. Adjust the path to the stylesheet.css
as is appropriate for your project.
To also activate Googles prettify.js
, add the following configuration properies to your configuration (assumes that the prettify.js
is in the specified location within the apidocs
folder:
<header><![CDATA[<script src="{@docRoot}/resources/prettify.js" type="text/javascript"></script>]]></header> <bottom><![CDATA[<script type="text/javascript">prettyPrint();</script>]]></bottom>
We have already blogged about how to integrate taglets in your Maven configuration. Please refer to Appealing API Documentation with taglet.
If you need Oracle's original CSS for the Javadoc report to start with, is is available at http://docs.oracle.com/javase/7/docs/api/stylesheet.css.
The configuration config-smartics-alias for the Alias Maven Plugin has been released with version 1.0.8.
There is only a minor change in the configuration to allow to switch console configuration to Java 5. Before only Java 6 and 7 were included.
The Alias Maven Plugin allows to configure shell aliases. In the past we provided two aliases to switch between different Java versions to start Maven build processes according to their requirements.
There is a better way to do this in Maven. But in the end there are – as often – some problems to fix. The Alias Maven Plugin may help!
Old Alias Configuration
With our alias configuration the created aliases were:
j6 = set JAVA_HOME=E:\java\edition\jdk1.6.0_25x64 j7 = set JAVA_HOME=E:\java\edition\jdk1.7.0
The trick has been to change the JAVA_HOME environment variable that was part of the PATH. Unfortunately, supposedly due to an obscure operating system update, this configuration does not work any longer. Fortunately, there is a Maven way to get the same result that is even more convenient than the alias we had used so far. It is called toolchains.
Maven Toolchains
The use of Maven Toolchains is well documented. Here is a very brief summary of the steps to configure toolchains for a Maven project:
toolchains.xml
Add a toolchains.xml
to your .m2
folder in your home folder (right next to your settings.xml
):
<?xml version="1.0"?> <toolchains> <toolchain> <type>jdk</type> <provides> <version>1.7</version> <vendor>sun</vendor> <id>1.7</id> </provides> <configuration> <jdkHome>D:\\java\\edition\\jdk1.7.0_05</jdkHome> </configuration> </toolchain> <toolchain> <type>jdk</type> <provides> <version>1.6</version> <vendor>sun</vendor> <id>1.6</id> </provides> <configuration> <jdkHome>D:\\java\\edition\\jdk1.6.0_25x64</jdkHome> </configuration> </toolchain> <toolchain> <type>jdk</type> <provides> <version>1.5</version> <vendor>sun</vendor> <id>1.5</id> </provides> <configuration> <jdkHome>D:\\java\\edition\\jdk1.5.0_22</jdkHome> </configuration> </toolchain> <toolchain> <type>jdk</type> <provides> <version>1.4</version> <vendor>sun</vendor> <id>1.4</id> </provides> <configuration> <jdkHome>D:\\java\\edition\\j2sdk1.4.2_19</jdkHome> </configuration> </toolchain> </toolchains>
maven-toolchains-plugin
Then add the Apache Maven Toolchains Plugin to the plugin’s section of your project’s pom.xml
file:
<plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-toolchains-plugin</artifactId> <version>1.0</version> <executions> <execution> <phase>validate</phase> <goals> <goal>toolchain</goal> </goals> </execution> </executions> <configuration> <toolchains> <jdk> <version>1.7</version> <vendor>sun</vendor> </jdk> </toolchains> </configuration> </plugin>
Lifecycle Mapping
You may also want to add the plugin to your lifecycle mappings to get rid of any warnings in your IDE. This is specified in your pom.xml
in the pluginManagement
section:
<pluginManagement> <plugins> <plugin> <groupId>org.eclipse.m2e</groupId> <artifactId>lifecycle-mapping</artifactId> <version>1.0.0</version> <configuration> <lifecycleMappingMetadata> <pluginExecutions> <pluginExecution> <pluginExecutionFilter> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-toolchains-plugin</artifactId> <versionRange>[0.0.0,)</versionRange> <goals> <goal>toolchain</goal > </goals> </pluginExecutionFilter> <action> <ignore/> </action> </pluginExecution> </pluginExecutions> </lifecycleMappingMetadata> </configuration> </plugin> </plugins> </pluginManagement>
Commandline Support
Since Maven 3 (alpha3) the location of the toolchain.xml
may be specified on the commandline.
-t,--toolchains <arg> Alternate path for the user toolchains file
This may be especially useful if you want to use a specific toolchains configuration on your build server for certain builds.
Reporting Projects
So everything just fine? No, unfortunately not. Not all plugins are toolchains-aware, especially our reporting plugins :-(, like the one of smartics Exceptions or smartics Properties, do not support toolchains.
If you start the build process on a project using exceptioncodes report and requiring Java 7 with a Java 6 VM, the following error is printed to the console:
Caused by: org.apache.maven.plugin.PluginContainerException: An API incompatibility was encountered while executing de.smartics.exceptions:exceptioncodes-maven-plugin:0.7.0-SNAPSHOT:export: java.lang.UnsupportedClassVersionError: de/smartics/exceptions/sample/MyCode : Unsupported major.minor version 51.0
has been started with. What we should have done is fork another VM according to the toolchain configuration and evaluate the project classes there.
So is this the end? Certainly not!
You may launch Maven with the most recent VM and due to backward compatibility get the desired result. But then you have – again – to fiddle with JAVA_HOME and the PATH. Reenter the Alias Maven Plugin …
Alias Plugin
Now the Alias Maven Plugin comes handy to let us change the Java version in our shell more easily. Therefore we made a small change to always adjust the PATH when we change JAVA_HOME.
doskey j6 = set JAVA_HOME=%JAVA_HOME_6%^&^& set PATH=%JAVA_HOME_6%\bin;%PATH% doskey j7 = set JAVA_HOME=%JAVA_HOME_7%^&^& set PATH=%JAVA_HOME_7%\bin;%PATH%
So please update to version 1.0.7 of config-smartics-alias to use this feature – until we finally fixed the toolchains issue in our reporting plugins.
The configuration config-smartics-alias for the alias-maven-plugin has been released with version 1.0.7.
This micro release fixes a problem with an update to windows systems that made them resistant to changes of environment variables used in the PATH variable.
Now the version of Java being used on the commandline can again be changed with j6
/j7
.