How to write I18N exceptions

This section describes the steps to implement exceptions with support for internationalization (I18N).

This howto introduces to the traditional way of writing exception where the exception class is also the container for the information about the exception event. In Message-based I18N exception we show how to separate the exception to be caught from the event information.

Audience

Note that most of the time the messages provided to exceptions are intended for the system operator or software developer as long as the presentation tier is not concerned. Displaying messages in the presentation tier often requires more structured text than simple text (e.g. messages shown in a web browser). Presentation tier messages should abstract from the messages provided to the exceptions caught from lower levels.

As a rule of thumb: Never display the exception text in the presentation tier to the user of the system. Just keep it inside the system's logging framework. Exception to this rule are exceptions that are part of the presentation tier. There will be a mapping between the exceptions on the presentation tier and those of lower tiers.

Security Issues

Displaying messages intended for the operator or software developer to the user will also introduce a security risk. Please refer to Information Leakage and Improper Error Handling on the OWASP Website for details.

Subclassing

Two base implementations of exceptions are provided by this library extension to smart-exceptions-core.

  1. de.smartics.exceptions.i18n.AbstractLocalizedException
  2. de.smartics.exceptions.i18n.AbstractLocalizedRuntimeException

Make your exceptions subclass one of them.

For details on implementing exception codes please refer to the documentation in the smart-exceptions-core project.

Provide a Message Bundle

For the localized information required by the exceptions provide a standard exception bundle. You may specify this information for the String property bundleBaseName explicitly as in "de.smartics.exceptions.i18n.message.ParseCodeBundle" or you may use the default location: Name of the code enumeration class (inclusive package name) and suffix Bundle.

A message bundle looks like this:

2000.title=Parse exception
2000=Cannot parse input ''{0}''.
2000.details=The system is unable to parse input ''{0}'' at position {1}.

2001.title=Parse exception
2001=Cannot parse OGNL path in ''{0}''.
2001.details=The system is unable to parse the OGNL path in ''{0}'' at \
 position {1}.

2002.title=Parse exception
2002=Cannot parse parent attribute since separator is missing: ''{0}''
2002.details=The '='-sign to separate the key from the value is missing at \
 position {1}.

2003.title=Parse exception
2003=Cannot parse parent attribute since value is missing: ''{0}''
2003.details=The value of the property attribute is missing at position {1}.

2004.title=Parse exception
2004=Cannot parse parent attribute since index is missing: ''{0}''
2004.details=The index of the attribute is missing at position {1}.

The key is the exception code (without the component prefix). The value is the localized information. Since the message usually contains place holders, the value is also called message template.

There are five distinct pieces of information for the exception text:

Level Description
title (TITLE) This information is intended to provide a simple category to be displayed in window titles. More than one message has the same title.
summary (SUMMARY , no label) The summary is a short explanation of what has happened.
details (DETAILS) The details information gives more details on what has happened. Typical usage of this message type is to add parameter values that were passed to the module that threw the exception of to give some technical details only relevant to users of the system that track the failure to its root cause.
implications (IMPLICATIONS_ON_CURRENT_TASK) The information about what implications the abort of the current task has on the work of the user.

While the summary and details section of the message explained what has happened, the implications show what this means for the current task being aborted. The reader of the message can e.g. be assured that his transaction has been rolled back and no money transfer has taken place.
todo (WHAT_TO_DO_NOW) The information about what the user can do now.

If the exception has been raised e.g. because of invalid user input this information can lead the user to what input has been expected. It can also give hints what configuration has lead to this problem in case a subsystem is not available.
url (URL) The URL that links to further information on the problem

The information references may provide even more details on the type of failure being reported. The referenced page may only have static content that does not quote parameters provided by the exception instance, but may provide structured information and links to related information on a help portal or FAQ.

The message type are defined in the de.smartics.exceptions.i18n.message.MessageType enumeration.

Instead of index values you may use any name that applies to the naming rules defined by the ICU MessageFormat specification.

But how are the values for the place holder provided? See the next section ...

Message Place Holder

Place holders are specified by annotating field members of an exception class with de.smartics.exceptions.i18n.message.MessageParam

To associate a field with the name of the field as the place holder in a message template write:

@MessageParam
private final String myField

In this case use {myField} in the message template.

To associate a field with an arbitrary name as the place holder in a message template write:

@MessageParam("specialName")
private final String myField

In this case use {specialName} in the message template.

To associate a field with the place holder 0 in a message template write (this is the only way supported up to version 0.9.1):

@MessageParam("0")
private final String myField

Since using indices is problematic if you inherit from another exception or message, it is recommended to use names from version 0.10.0 on.

The syntax for the value of the annotation is based on OGNL. The index of the place holder is separated from the OGNL expression by a colon (:):

index:ognl-expression

So if for a account that provides access to a user bean that provides access to a name attribute, the MessageParam to display only that name for placeholder 1 would be:

@MessageParam("1:user.name")
private final Account myField

There is a short version in case you have an OGNL and want the same name as the last part of the OGNL path as the name of the place holder:

@MessageParam(":user.name")
private final Account myField

In this case use {name} in the message template.

This form is especially useful, if your path has only one element. Instead of writing name:name (which is also valid), simply type:

@MessageParam(":name")
private final Account myField

If several pieces of information is to be drawn from a single field, use the comma (,) to separate them:

@MessageParam("0:user.name,2:user.id,3:description")
protected final Context context;
@MessageParam("1")
protected final Date date;

The resource bundle may provide the following message template:

{1,date} at {1,time}: For user ''{0}'' (ID={2}) this is the description: {3}} 

Please note that currently there is a public getter required for the property to be read at runtime.

Have a look at ParseException for a live example on how to use the MessageParam annotation.

Access parent information

If you want to include information from the parent (such as the message or the cause) you have to provide a specific annotation at class level. This annotation is called de.smartics.exceptions.i18n.message.ParentMessageParam . Please note that this annotation is currently not inherited to subclasses.

Typically one need to access the message from the cause. Use this to make that message text available for place holder 0:

@ParentMessageParam("cause=0:message")
public class MyException extends RuntimeException { ...}

Any property may be accessed. The following example draws information from two properties (separated by semi-colons ;).

@ParentMessageParam("cause=1:message,3:message,4:localizedMessage; message=5")
public class MyOtherException extends AbstractLocalizedRuntimeException { ...}

The parent property cause provides the information (message) for placeholder 1 and 3 (just to show how the same information can be used for different place holders), the localizedMessage provides information for placeholder 4 and the parent's message is used to supply a value for place holder 5. Please note that usually you only need to access properties from parents that are not smartics-exceptions, since the MessageParama from parent classes are automatically visible.

Please note that from version 0.10.0 on you may use any string identifier instead of an index number.

Have a look at MethodAccessConfigurationException for a live example on how to use the ParentMessageParam annotation.

Optional: Mapping Code to Exception Class

For some reports it is necessary to give the documentation tool a reference from the code to the exception class that provides information for the place holders in the message templates. This exception class is called Message Params Descriptor, since it describes the values provided by the exception to be used for the place holders in the message templates.

The SDoc mojo of the exceptioncodes-maven-plugin exports the place holder documentation, if the enumeration element of the code is annotated with the de.smartics.exceptions.i18n.message.MessageParamsDescriptor annotation.

@MessageParamsDescriptor(MyException.class)
MyCode(1000)

Suppose the message template is defined like this:

1000.title=Test Exception Zero
1000=The name is set to {1}. URL is ''{2}:{3}'': {0}
1000.details=No details for the test.

And the exceptions are given as

@ParentMessageParam("cause=0:message")
public class MyException extends BaseException {
  ...
  /**
   * The test URL.
   *
   * @serial
   */
  @MessageParam("2:host,3:port")
  private final URL url;
  ...
}
public class BaseException
  extends AbstractLocalizedRuntimeException {
  /**
   * The name of the test exception.
   *
   * @serial
   */
  @MessageParam("1")
  private final String name;
  ...
}

The report part, generated by SDoc mojo, that displays the messages and information to their place holders looks like this:

<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>