J2SE 1.4 premieres Java's assertion capabilities, Part 1

Understand the mechanics of Java's new assertion facility

Assertions have been in the software engineering canon for many years, most notably as the centerpiece of the Design by Contract facility that Bertrand Meyer built into his Eiffel programming language. Assertions date back at least as far as the 1967 article "Assigning Meanings to Programs" (Proceedings of the Symposium on Applied Mathematics, Vol. 19, pp. 19-32; American Mathematical Society, 1967), in which Robert Floyd discussed using assertions to systematically prove program correctness. Correctness pertains to a system's adherence to a specification and complements the other major reliability attribute, robustness, which pertains to a system's ability to handle abnormal conditions.

As I will explain in Part 2 of this series, assertions help implement correct programs. Assertions were actually part of Oak, an early version of Java, but were jettisoned in a final push to get Java out of the lab and into the hands of Internet developers. As part of the Java Community Process, Java Specification Request 41 proposed adding a simple assertion facility to Java, prompting its welcome reappearance in J2SE (Java 2 Platform, Standard Edition) version 1.4.

An assertion is a boolean expression that a developer specifically proclaims to be true during program runtime execution. The simple idea of using assertions can have an unexpected influence on a software program's design and implementation. In this article I cover the mechanics of using the new assertion facility introduced in J2SE 1.4. In Part 2 I will cover the methodology of using assertions.

Read the whole series on J2SE 1.4's assertion capabilities:

Declare an assertion

You declare assertions with a new Java language keyword, assert. An assert statement has two permissible forms:

  1. assert expression1;
  2. assert expression1 : expression2;

In each form, expression1 is the boolean-typed expression being asserted. The expression represents a program condition that the developer specifically proclaims must be true during program execution. In the second form, expression2 provides a means of passing a String message to the assertion facility. The following are a few examples of the first form:

  1. assert 0 < value;
  2. assert ref != null;
  3. assert count == (oldCount + 1);
  4. assert ref.m1(parm);

The asserted expression must be of type boolean, which the first three expressions obviously are. In the fourth expression, the method call m1(parm) must return a boolean result. A compile-time error occurs if expression1 does not evaluate to type boolean.

As an example of using assertions, class Foo listed below contains a simple assertion in the method m1(int):

public class Foo
{
  public void m1( int value )
  {
    assert 0 <= value;
    System.out.println( "OK" );
  }
  public static void main( String[] args )
  {
    Foo foo = new Foo();
    System.out.print( "foo.m1(  1 ): " );
    foo.m1( 1 );
    System.out.print( "foo.m1( -1 ): " );
    foo.m1( -1 );
  }
}

The method main() calls m1(int) twice, once with a positive value and once with a negative value. The call with the negative value triggers an assertion error. Since assert is a new Java keyword, to see this example in action, you must compile the class with a J2SE 1.4-compliant compiler. Furthermore, the compiler requires a command-line option, -source 1.4, to signal source compilation using the assertion facility. Requiring a command-line switch to include assertions purportedly protects backward compatibility.

By default (that is, in the absence of the -source 1.4 switch), a J2SE 1.4 compiler does not allow assert statements. However, the compiler complains if the source code uses the assert keyword as an identifier or label. That means a J2SE 1.4 compiler rejects prior Java source files that use assert in this manner, even though the source compiled successfully under J2SE 1.3 or an earlier compiler. Note that this does not affect previously compiled class files.

The following command compiles Foo.java:

javac -source 1.4 Foo.java

The resulting Foo.class file contains assertion code in the method m1(int). But, just as the compiler does not, by default, include the assertion facility, the java command does not, by default, enable assertions. In other words, assertions are disabled in the Java runtime environment by default.

The default behavior of the compiler and runtime system seems backward to me. Assertions are an important enough addition to the Java language that they should be included and enabled by default. The compile-line option should be -source 1.3 for the backward compatibility of not including the assertion facility, and the java command should enable assertions by default. I appreciate the pressure to preserve backward compatibility, but assertions prove too important to be relegated to a special, nondefault case. But I'll get off my soapbox now and continue.

Enable assertions

Command-line options to the java command allow enabling or disabling assertions down to the individual class level. The command-line switch -enableassertions, or -ea for short, enables assertions. The switch (I use the short form) has the following permissible forms:

  1. -ea
  2. -ea:<class name>
  3. -ea:...
  4. -ea:<package name>...

The first form enables assertions in all classes except system classes. A separate switch, -enablesystemsassertions, or -esa for short, enables system class assertions. System classes warrant a separate switch because developers rarely have occasion to suspect assertion errors in the Java system libraries.

The second form turns on assertions for the named class only. The last two forms enable assertions at the package level. The third enables assertions for the default, or unnamed, package, and the fourth enables assertions for the specified package name.

Take care in using the second and fourth forms, since class and package names are not verified for existence. As we'll see later in this article, the class ClassLoader maintains a mapping of class and package names to desired assertion status. When a ClassLoader subclass loads a class, the mappings determine the setting of a special assertions-enabled flag in each class. Any mappings for nonexistent classes or packages are simply never accessed. In particular, the runtime system silently interprets a package name without a trailing "..." as a class name.

Note that the syntax for enabling assertions at the package level uses ... rather than the expected *. The trailing ... serves as reminder of another assertion facility aspect: enabling assertions at the package level actually turns on assertions for that package and all subpackages. For example, the command-line switch -ea:javax.swing... enables assertions for all Swing packages, and the command-line switch -ea:javax.xml... enables assertions for the five J2SE 1.4 XML packages. Interestingly, in the latter example the five XML packages are considered javax.xml subpackages even though javax.xml is not, itself, a package.

The following shows the result of running Foo with the command java Foo, in which the default runtime environment disables assertion checking:

foo.m1(  1 ): OK
foo.m1( -1 ): OK

With assertions disabled, neither m1(int) method call triggers an assertion. As previously described, enabling assertion requires the use of a command-line switch. Any of the following commands enables assertion checking in class Foo:

java -ea     Foo
java -ea:Foo Foo
java -ea:... Foo

The following shows the resulting output:

foo.m1(  1 ): OK
foo.m1( -1 ): Exception in thread "main" java.lang.AssertionError
        at Foo.m1(Foo.java:6)
        at Foo.main(Foo.java:17)

Calling method m1(int) with the parameter 1 fails to trigger the assertion. However, passing -1 violates the assertion that the parameter must be a positive integer. The Java runtime system reports the failed assertion through the use of a new class, java.lang.AssertionError.

Class AssertionError

The new assertion facility adds the class AssertionError to the java.lang package. AssertionError contains a default constructor and seven single-parameter constructors. The assert statement's single-expression form uses the default constructor, whereas the two-expression form uses one of the seven single-parameter constructors.

To understand which AssertionError constructor is used, consider how assertions are processed when enabled:

Evaluate expression1

  • If true
    • No further action
  • If false
    • And if expression2 exists
      • Evaluate expression2 and use the result in a single-parameter form of the AssertionError constructor
    • Else
      • Use the default AssertionError constructor

Since the assert statement in class Foo uses the single-expression form, the violation triggered in passing -1 to method m1(int) prompts the use of the default AssertionError constructor. The default constructor effectively uses java.lang.Throwable's default constructor to print an exception message that includes a textual stack trace description.

The assertion error output from running class Foo is lacking. We see that an AssertionError occurs in method m1 at line 6, but the output does not describe what went wrong. Fortunately, the assert statement's two-expression form provides this facility. As noted above, in the two-expression form, when expression1 evaluates as false, the assertion facility passes the result of evaluating expression2 to a single-parameter AssertionError constructor. Expression2 effectively acts as a String message carrier, meaning AssertionError's single-parameter constructors must convert the result of expression2 to a String. Covering all expression result-type possibilities requires a separate constructor for each of the seven string conversion rules detailed in The Java Language Specification, Section 15.18.1.1. Fortunately, developers can ignore this implementation detail and simply focus on using expression2 as a message carrier for describing the assertion error.

Class Bar, listed below, uses the two-parameter form for a simple assertion in method m1(int):

public class Bar
{
  public void m1( int value )
  {
    assert 0 <= value : "Value must be non-negative: value= " + value;
    System.out.println( "OK" );
  }
  public static void main( String[] args )
  {
    Bar bar = new Bar();
    System.out.print( "bar.m1(  1 ): " );
    bar.m1( 1 );
    System.out.print( "bar.m1( -1 ): " );
    bar.m1( -1 );
  }
}

The following shows Bar's output with assertions enabled:

bar.m1(  1 ): OK
bar.m1( -1 ): Exception in thread "main" java.lang.AssertionError: Value
must be non-negative: value= -1
        at Bar.m1(Bar.java:6)
        at Bar.main(Bar.java:17)

The output shows the String conversion of the expression2 result concatenated to the end of the exception message, before the textual stack trace. The detailed message certainly improves the exception message's usability. Since creating a reasonable error message is not difficult, developers should favor the assert statement's two-expression form.

As an aside, J2SE 1.4 also adds new capabilities to the java.lang.Throwable class that enable a cleaner formatting of stack trace information. The class FooBar listed below uses these new capabilities to format the exception message produced by the assertion error:

public class FooBar
{
  public void m1( int value )
  {
    assert 0 <= value : "Value must be non-negative: value= " + value;
    System.out.println( "OK" );
  }
  public static void printAssertionError( AssertionError ae )
  {
    StackTraceElement[] stackTraceElements = ae.getStackTrace();
    StackTraceElement stackTraceElement = stackTraceElements[ 0 ];
    System.err.println( "AssertionError" );
    System.err.println( "   class=   " + stackTraceElement.getClassName() );
    System.err.println( "   method=  " + stackTraceElement.getMethodName() );
    System.err.println( "   message= " + ae.getMessage() );
  }
  
  public static void main( String[] args )
  {
    try
    {
      FooBar fooBar = new FooBar();
      System.out.print( "fooBar.m1(  1 ): " );
      fooBar.m1( 1 );
      System.out.print( "fooBar.m1( -1 ): " );
      fooBar.m1( -1 );
    }
    catch( AssertionError ae )
    {
      printAssertionError( ae );
    }
  }
}

The following output of running FooBar with assertions enabled displays the cleaner AssertionError reporting:

fooBar.m1(  1 ): OK
fooBar.m1( -1 ): AssertionError
   class=   FooBar
   method=  m1
   message= Value must be non-negative: value= -1

Assertions and inheritance

Assertions can also be disabled down to the class level. The switch -disableassertions, or -da for short, parallels the syntax of the assertion-enabling switch. A command line can contain as many enable- and disable-assertion switches as desired.

Since you can set assertion status at the class level, you can enable assertions for a particular class and disable them for that class's superclass. The classes Base and Derived, listed below, show the effect of differing assertion statuses in an inherited implementation:

// Base.java
//
package tmp;
public class Base
{
  public void m1( boolean test )
  {
    assert test : "Assertion failed: test is " + test;
    System.out.println( "OK" );
  }
}
// Derived.java
//
package tmp.sub;
import tmp.Base;
public class Derived
  extends Base
{
  public void m2( boolean test )
  {
    assert test : "Assertion failed: test is " + test;
    System.out.println( "OK" );
  }
  public static void printAssertionError( AssertionError ae )
  {
    StackTraceElement[] stackTraceElements = ae.getStackTrace();
    StackTraceElement stackTraceElement = stackTraceElements[ 0 ];
    System.err.println( "AssertionError" );
    System.err.println( "   class=   " + stackTraceElement.getClassName() );
    System.err.println( "   method=  " + stackTraceElement.getMethodName() );
    System.err.println( "   message= " + ae.getMessage() );
  }
  
  public static void main( String[] args )
  {
    try
    {
      Derived derived = new Derived();
      System.out.print( "derived.m1( false ): " );
      derived.m1( false );
      System.out.print( "derived.m2( false ): " );
      derived.m2( false );
    }
    catch( AssertionError ae )
    {
      printAssertionError( ae );
    }
  }
}

Each class contains a simple method with an assertion statement. Note that class Derived inherits m1(boolean)'s Base class implementation.

First, I run tmp.sub.Derived with assertions enabled for all classes:

java -ea tmp.sub.Derived
derived.m1( false ): AssertionError
   class=   tmp.Base
   method=  m1
   message= Assertion failed: test is false

The AssertionError message reports an exception in the implementation inherited from class tmp.Base. This is, of course, the basis of implementation inheritance.

Next, I run tmp.sub.Derived with assertions enabled by default, but specifically disabled for the class tmp.sub.Base:

java -ea -da:tmp.Base tmp.sub.Derived
derived.m1( false ): OK
derived.m2( false ): AssertionError
   class=   tmp.sub.Derived
   method=  m2
   message= Assertion failed: test is false

Now the method call derived.m1(false) fails to trigger the assertion, since the implementation resides in class tmp.Base and assertions are disabled for that class. Although this proves quite sensible, it could be surprising at first.

As a final comment on the command-line switches for enabling and disabling assertions, note in the command above that the class-specific switch -da:tmp.Base overrides the global switch -ea. Assertion status for each class is determined by the most specific setting. Furthermore, command-line switches are processed in order, with later switches taking precedence over earlier switches. To understand how class assertion status is maintained, I next investigate the assertion-enabling additions to java.lang.ClassLoader.

Additions to java.lang.ClassLoader

To maintain assertion status at the class and package levels, J2SE 1.4 includes several additions to class java.lang.ClassLoader. The new public methods are listed below, along with a brief description of their role in the new assertion facility:

public void setClassAssertionStatus(String className, boolean enabled)
ClassLoader maintains a map of class names to assertion status. This method corresponds to the command-line switches -ea:<class name> and -da:<class name>, which enable and disable assertions for specifically named classes.
public void setPackageAssertionStatus(String packageName, boolean enabled)
ClassLoader also maintains a map of package names to assertion status. This method corresponds to the command-line switches -ea:<package name>... and -da:<package name>..., which enable and disable assertions for named packages and all subpackages.
public void setDefaultAssertionStatus(boolean)
ClassLoader maintains a default assertion status. This method corresponds to the command-line switches -ea and -da for enabling and disabling assertions for all nonsystem classes, and the switches -esa and -dsa for enabling and disabling assertions in the system class loader.

A new, special assertion-status flag is set in each class loaded by a ClassLoader subclass. The simple assertion-status resolution scheme used by ClassLoader is as follows:

  • Check the class name map for a class-specific assertion-status mapping.
  • Check the package name map for a mapping of the class's most specific package name. Repeat for each next-most specific package name of the class.
  • Use the default assertion-status setting.

The resolution scheme stops on first match or when the default assertion status is reached. As an example, when loading class tmp.sub.Derived, the scheme completes the following steps:

  • Checks the class name map for entry tmp.sub.Derived.
  • Checks the package name map for entry tmp.sub.
  • Checks the package name map for entry tmp.sub.
  • Uses the default assertion-status setting.

This level of implementation detail demystifies the determination of class assertion status and explains several previously made comments:

  • The command-line switches don't verify the existence of class and package names. They simply register entries into the maps maintained by ClassLoader.
  • The most specific mapping takes precedence, since the assertion-status resolution scheme stops on the first map entry match and the scheme checks from most specific to least specific.
  • Later command-line switches take precedence over earlier switches, since map entries in ClassLoader are simply being overwritten.

As a final note on the implementation detail, ClassLoader subclasses set a class's assertion-status flag when loading the class. Adding or modifying class or package name mappings using ClassLoader methods does not affect already loaded classes. For example, suppose myClassLoader loads class tmp.sub.Derived, no class- or package-specific mappings exist, and the default assertion status is false. A programmatic call to myClassLoader.setClassAssertionStatus("tmp.sub.Derived",true) does not enable assertion in the already loaded class tmp.sub.Derived. If myClassLoader later reloads that class, then the assertion status will reflect the programmatic change. Fortunately, this is only of concern for programmatic changes and does not affect the more common usage of setting assertion statuses using command-line switches.

Miscellaneous details

The assertion facility does not provide a direct means of conditionally preventing the inclusion of assertion code in a class file. Section 14.20 of The Java Language Specification, however, provides a standard idiom for achieving conditional compilation in Java. Applied to assertions, the idiom looks something like this:

static final boolean assertionsEnabled = <true | false>;
if( assertionsEnabled )
  assert expression1;

Since the variable assertionsEnabled is static final, when the value is false, a compiler can detect that the assert statement is unreachable and can remove the assert statement code.

The assertion facility also does not provide a means of querying the assertion-status flag in a class. As a slight abuse to the assertion facility, however, the following idiom sets variable assertionsEnabled to the current class assertion status:

boolean assertionsEnabled = false;
assert assertionsEnabled = true;

This idiom abuses the assertion facility by using an assertion to achieve a desired side effect. Expressions within an assert statement should not produce side effects, since doing so exposes program execution to potentially different behavior with and without assertions enabled. You should use assertions to produce more reliable programs, not less reliable ones.

Finally, caution must guide the development of the expressions used in assert statements. In addition to not producing side effects, assertions should not alter normal program execution. As a pathological example, the following class enters an infinite recursion when assertions are enabled and only stops when a StackOverflowError brings the program to a crashing halt:

public class Pathological
{
  private String ping( boolean test )
  {
    System.err.print( "ping ... " );
    assert test : pong( test );
    return null;
  }
  private String pong( boolean test )
  {
    System.err.print( "pong ... " );
    assert test : ping( test );
    return null;
  }
  public static void main( String[] args )
  {
    Pathological pathological = new Pathological();
    System.err.println( "Pathological.ping( false ): " );
    pathological.ping( false );
  }
}

Though highly stylized, this example hints at the dangers of writing complex and oblique assert statements.

Be assertive

Assertions are an important addition to the Java programming language. By using assertions, developers can clearly demark the boundaries of acceptable program behavior.

This article exhaustively details the mechanics of using J2SE 1.4's assertions. Fortunately, you only need to digest most of these sticky details once. Using the simple assertion facility is relatively straightforward, so you can quickly add assertions to your development routine. Regrettably, neither the J2SE 1.4 compiler nor its runtime system enable the assertion facility by default. Each requires new command-line switches.

Part 2 of this article will discuss the methodological issues regarding assertions and how the Java assertion facility compares to Design by Contract. It also challenges the convention of using the Java exception mechanism for handling argument checking in public methods. Until then, remember to be assertive with your assertions.

Wm. Paul Rogers is a Java/object-oriented architect whose interests include teaching an understanding of Java design and implementation through stressing the first principles of fundamental object-oriented programming. He began using Java in the fall of 1995 in support of oceanographic studies conducted at the Monterey Bay Aquarium Research Institute, where he led the charge in utilizing new technologies to expand the possibilities of ocean science research. Paul has been using object-oriented methods for 10 years and works as an independent consultant in Bellingham, Wash., where he also teaches computer science at Western Washington University.

Learn more about this topic

  • Browse our Topical Index for more stories on the Java 2 Platform, Standard Edition: http://www.javaworld.com/channel_content/jw-j2se-index.shtml
  • Read more JavaWorld articles by Wm. Paul Rogers

Join the discussion
Be the first to comment on this article. Our Commenting Policies