Using Assertions in Java Technology

   
By Qusay H. Mahmoud, June 14, 2005  

Finding bugs in programs is not an easy task, especially when they are subtle bugs. The process of finding bugs is not exciting, and you may have gone through your program wasting your time trying to find bugs that should not have existed in the first place. Such bugs may exist because you did not understand the specifications correctly. And as Barry Boehm, the father of software economics, put it: If it costs $1 to find and fix a requirement-based problem during the requirements definition process, it can cost $5 to repair it during design, $10 during coding, $20 during unit testing, and as much as $200 after delivery of the system. Thus, finding bugs early on in the software life cycle pays off.

You can use assertions to detect errors that may otherwise go unnoticed. Assertions contain Boolean expressions that define the correct state of your program at specific points in the program source code. The designers of the Java platform, however, didn't include support for assertions. Perhaps they viewed exceptions as a superior feature, allowing you to use try/ catch/ finally to throw an exception instead of aborting the program as in assertions. But the Java 2 Platform, Standard Edition (J2SE) release 1.4, has introduced a built-in assertion facility. This article does the following:

  • Presents an overview of design by contract
  • Presents an overview of assertions
  • Shows how to roll your own assertion capabilities
  • Describes the new assertion facility
  • Shows how to use the new assertion facility
  • Offers guidelines for using assertions
  • Presents examples of how to use assertions
Design by Contract

We can view program correctness as proof that the computation, given correct input, terminated with correct output. The user invoking the computation has the responsibility of providing the correct input, which is a precondition. If the computation is successful, we say that the computation has satisfied the postcondition. Some programming languages (such as Eiffel) encourage developers to provide formal proof of correctness by writing assertions that may appear in the following roles:

  • Precondition: A condition that the caller of an operation agrees to satisfy
  • Postcondition: A condition that the method itself promises to achieve
  • Invariant: A condition that a class must satisfy anytime a client could invoke an object's method, or a condition that should always be true for a specified segment or at a specified point of a program

These three roles collectively support what is called the design-by-contract model of programming, a model that is well supported by the Eiffel programming language. Java technology, on the other hand, doesn't have built-in support for the design-by-contract model of programming. In fact, the Java platform did not have built-in support for assertions until the release of J2SE 1.4.

Assertions

An assertion has a Boolean expression that, if evaluated as false, indicates a bug in the code. This mechanism provides a way to detect when a program starts falling into an inconsistent state. Assertions are excellent for documenting assumptions and invariants about a class. Here is a simple example of assertion:

BankAccount acct = null;

// ...
// Get a BankAccount object
// ...

// Check to ensure we have one
              assert acct != null;
          

This asserts that acct is not null. If acct is null, an AssertionError is thrown. Any line that executes after the assert statement can safely assume that acct is not null.

Using assertions helps developers write code that is more correct, more readable, and easier to maintain. Thus, assertions improve the odds that the behavior of a class matches the expectations of its clients.

Note that assertions can be compiled out. In languages such as C/C++, this means using the preprocessor. In C/C++, you can use assertions through the assert macro, which has the following definition in ANSI C:

void assert(int expression)

The program will be aborted if the expression evaluates to false, and it has no effect if the expression evaluates to true. When testing and debugging is completed, assertions do not have to be removed from the program. However, note that the program will be larger in size and therefore slower to load. When assertions are no longer needed, the line

#define NDEBUG

is inserted at the beginning of the program. This causes the C/C++ preprocessor to ignore all assertions, instead of deleting them manually.

In other words, this is a requirement for performance reasons. You should write assertions into software in a form that can be optionally compiled. Thus, assertions should be executed with the code only when you are debugging your program -- that is, when assertions will really help flush out errors. You can think of assertions as a uniform mechanism that replaces the use of ad hoc conditional tests.

Implementing Assertions in Java Technology

J2SE 1.3 and earlier versions have no built-in support for assertions. They can, however, be provided as an ad hoc solution. Here is an example of how you would roll your own assertion class.

Here we have an assert method that checks whether a Boolean expression is true or false. If the expression evaluates to true, then there is no effect. But if it evaluates to false, the assert method prints the stack trace and the program aborts. In this sample implementation, a second argument for a string is used so that the cause of error can be printed.

Note that in the assert method, I am checking whether the value of NDEBUG is on ( true) or off ( false). If NDEBUG sets to true, then the assertion is to be executed. Otherwise, it would have no effect. The user of this class is able to set assertions on or off by toggling the value of NDEBUG. Code Sample 1 shows my implementation.

Code Sample 1: Assertion.java

public class Assertion {

   public static boolean NDEBUG = true;

   private static void printStack(String why) {
      Throwable t = new Throwable(why);
      t.printStackTrace();
      System.exit(1);
   }

   public static void assert(boolean expression, String why) {
      if (NDEBUG && !expression) {
         printStack(why);
      }
   }
}
 
Note: In order for Code Sample 1 to compile, use -source 1.3 because assert is a keyword as of J2SE 1.4. Otherwise, you will get the following error message:
C:\CLASSES>javac Assertion.java
Assertion.java:11: as of release 1.4, 'assert' is a keyword, and may not be used
 as an identifier
(try -source 1.3 or lower to use 'assert' as an identifier)
   public static void assert(boolean expression, String why) {
                      ^
1 error

Code Sample 2 demonstrates how to use the Assertion class. In this example, an integer representing the user's age is read. If the age is greater than or equal to 18, the assertion evaluates to true, and it will have no effect on the program execution. But if the age is less than 18, the assertion evaluates to false. The program then aborts, displays the message You are too young to vote, and shows the stack trace.

Note: It is important to note that in this example assertions are used to validate user input and that no invariant is being tested or verified. This is merely to demonstrate the use of assertions.

Code Sample 2: AssertionTest1.java

import java.util.Scanner;
import java.io.IOException;

public class AssertionTest1 {
   public static void main(String argv[]) throws IOException {
      Scanner reader = new Scanner(System.in);      
      System.out.print("Enter your age: ");
      int age = reader.nextInt();
      //Assertion.NDEBUG=false;
      Assertion.assert(age>=18, "You are too young to vote");
      // use age
      System.out.println("You are eligible to vote");
   }
}


 

Figure 1 shows a couple of sample runs of AssertionTest1.

 

figure 1
Figure 1: Sample Run of AssertionTest1


Let's look at another example in which assertions can be very helpful. If you have a switch statement with no default case, you are assuming that one of the cases will always be executed. To test this assumption, you can add an assertion to the default case as shown in Code Sample 3. In this example, the user is asked to enter one letter denoting marital status. If the input is not one of the cases, then the default case will be executed.

Code Sample 3: AssertionTest2.java

import java.io.IOException;

public class AssertionTest2 {

   public static void main(String argv[]) throws IOException {
      System.out.print("Enter your marital status: ");
      int c = System.in.read();
      //Assertion.NDEBUG=false;
      switch ((char) c) {
         case 's':
         case 'S': System.out.println("Single"); break;
         case 'm':
         case 'M': System.out.println("Married"); break;
         case 'd':
         case 'D': System.out.println("Divorced"); break;
         default: Assertion.assert(!true, "Invalid Option"); break;
      }

   }
}
 

If you run AssertionTest2, you would see something similar to Figure 2.

 

figure 2
Figure 2: Sample Run of AssertionTest2


As I mentioned earlier, developers and users should have an option for turning assertions on and off. In the three examples I've provided, if you do not want assertions to be executed as part of the code, uncomment the line

Assert.NDEBUG = false;

Note: In this example, you cannot simply turn assertions on or off at runtime. You need to recompile the code in order to turn assertions off. As you have probably realized, adding an ad hoc assertions solution to Java technology is not a totally efficient approach. This is mainly because Java technology does not have a preprocessor. It is possible to think about other means of turning assertions off at runtime.
Assertion Facility in J2SE 1.4 and Later

As you have seen, the Assertion class is one way to add assertions to Java technology. Other developers may do it differently, and you might end up doing it in a different way. Each ad hoc implementation has its own means of enabling and disabling assertions. However, it is best to have the assertion facility built into the language itself. The assertion facility built into J2SE 1.4 (and later versions) provides a unified solution. More importantly, it allows you to turn assertions on or off at runtime.

The important thing to note about the assertion facility is that it is not provided as a user-level class library. Rather, it is built into the language by introducing a new keyword statement to Java technology: assert.

The assertion facility in J2SE 1.4 and later versions is not a full-blown design-by-contract facility. Adding such a facility would require substantial changes to the language and might subvert the simplicity of Java technology. However, the simple assertion facility provides a limited form of design-by-contract style programming.

Using Assertions

Use the assert statement to insert assertions at particular points in the code. The assert statement can have one of two forms:

assert booleanExpression;
assert booleanExpression : errorMessage;
 

The errorMessage is an optional string message that would be shown when an assertion fails.

As an example, I will modify Code Sample 3 to use the assert statement instead of my Assertion class, as shown in Code Sample 4.

Note: The example here relies on the user's input. Assertions, however, should be used to check for cases that should never happen, check assumptions about data structures (such as ensuring that an array is of the correct length), or enforcing constraints on arguments of private methods.

Code Sample 4: AssertionTest3.java

import java.io.*;

public class AssertionTest3 {

   public static void main(String argv[]) throws IOException {
      System.out.print("Enter your marital status: ");
      int c = System.in.read();
      switch ((char) c) {
         case 's':
         case 'S': System.out.println("Single"); break;
         case 'm':
         case 'M': System.out.println("Married"); break;
         case 'd':
         case 'D': System.out.println("Divorced"); break;
         default: assert !true : "Invalid Option"; break;
      }

   }
}

 
Note: if you are using J2SE 1.4.x (or later versions) to compile AssertionTest3, make sure you use the -source option as follows:
prompt> javac -source 1.4 AssertionTest3.java
If you try to compile your assertion-enabled classes without using the -source 1.4 option, you will get a compiler error saying that assert is a new keyword as of release 1.4.

If you now run the program using the command

prompt> java AssertionTest3

and you enter a valid character, it will work fine. However, if you enter an invalid character, nothing will happen. This is because, by default, assertions are disabled at runtime. To enable assertions, use the switch -enableassertion (or -ea) as follows:

prompt> java -ea AssertionTest3
prompt> java -enableassertion AssertionTest3

Following is a sample run:

C:\CLASSES>java -ea AssertionTest3
Enter your marital status: w
Exception in thread "main" java.lang.AssertionError: Invalid Option
        at AssertionTest3.main(AssertionTest3.java:15)

When an assertion fails, it means that the application has entered an incorrect state. Possible behaviors may include suspending the program or allowing it to continue to run. A good behavior, however, might be to terminate the application, because it may start functioning inappropriately after a failure. In this case, when an assertion fails, an AssertionError is thrown.

Note: By default, assertions are disabled, so you must not assume that the Boolean expression contained in an assertion will be evaluated. Therefore, your expressions must be free of side effects.

The switch -disableassertion (or -da) can be used to disable assertions. This, however, is most useful when you wish to disable assertions on classes from specific packages. For example, to run the program MyClass with assertions disabled in class Hello, you can use the following command:

prompt> java -da:com.javacourses.tests.Hello MyClass

And to disable assertions in a specific package and any subpackages it may have, you can use the following command:

prompt> java -da:com.javacourses.tests... MyClass

Note that the three-dot ellipsis ( ...) is part of the syntax.

Switches can be combined. For example, to run a program with assertions enabled in the com.javacourses.tests package (and any subpackages) and disabled in the class com.javacourses.ui.phone, you can use the following command:

prompt> java -ea:com.javacourses.tests... -da:com.javacourses.ui.phone MyClass

Note that when switches are combined and applied to packages, they are applied to all classes, including system classes (which do not have class loaders). But if you use them with no arguments ( -ea or -da), they do not apply to system classes. In other words, if you use the command

prompt> java -ea MyClass

then assertions are enabled in all classes except system classes. If you wish to turn assertions on or off in system classes, use the switches -enablesystemassertions (or -esa) and -disablesystemassertions (or -dsa).

Using Assertions for Design by Contract

The assertion facility can help you in supporting an informal design-by-contract style of programming. We will now see examples of using assertions for preconditions, postconditions, and class invariants. The examples are snippets of code from an integer stack, which provides operations such as push to add an item on the stack and pop to retrieve an item from the stack.

Preconditions

In order to retrieve an item from the stack, the stack must not be empty. The condition that the stack must not be empty is a precondition. This precondition can be programmed using assertions as follows:

 

public int pop() {
   // precondition
    
             assert !isEmpty() : "Stack is empty";
   return stack[--num];
}
          
Note: Because assertions might be disabled in some cases, precondition checking can still be performed by checks inside methods that result in exceptions such as IllegalArgumentException or NullPointerException.

Postconditions

In order to push an item on the stack, the stack must not be full. This is a precondition. To add an item on the stack, we assign the element to be added to the next index in the stack as follows:

stack[num++] = element;

However, if you make a mistake and you write this statement as

stack[num] = element

then you have a bug. In this case, we need to ensure that invoking the push operation is working correctly. So the postcondition here is to ensure that the new index in the stack is the old index plus one. Also, we need to make sure that the element has been added on the stack. The following snippet of code shows the push operation with a precondition and a postcondition.

public void push(int element) {
   // precondition
   assert num<capacity : "stack is full";
   int oldNum = num;
   stack[num] = element;
   // postcondition
   assert num == oldNum+1 && stack[num-1] == element : "problem with counter";
}

Note that if a method has multiple return statements, then postconditions should be evaluated before each of these return statements.

Class Invariants

A class invariant is a predicate that must be true before and after any method completes. In the stack example, the invariant would be that the number of elements in the stack is greater than or equal to zero and the number of elements should not exceed the maximum capacity of the class. These conditions, for example, can be coded as follows:

private boolean inv() {
  return (num >= 0 && num < capacity);
}

To check that the stack should satisfy this predicate at all times, each public method and constructor should contain the assertion

assert inv();

right before each return.

Source Compatibility

The new assert statement in Java technology will cause compatibility problems with existing Java source programs that use assert as an identifier. Binaries (.class files) that use the assert as an identifier, however, will continue to work fine. If you have a source file that uses an assert as an identifier, you will get a warning message at compile time saying that assert is now a keyword and you should not be using it as an identifier.

Conclusion

When writing programs, it is a good idea to put checks at strategic places for violations of basic assumptions. These checks help in debugging code. The new assertion facility in J2SE 1.4 (and later versions) provides a unified support for assertions in Java technology as well as a convenient way for developers both to turn assertions on and off as needed and to abort Java programs while printing a message that states where in the program the error was detected. Remember that these messages are for us -- developers -- and not users.

Although the use of assertions replaces the ad hoc use of conditional tests with a uniform methodology, it does not allow for a repair strategy to continue program execution. This means that when an exception is detected, the program aborts with no recovery mechanism. Nevertheless, assertions play an important role in debugging and designing code with testability in mind. The assertion facility can be used to support an informal design-by-contract style of programming.

For More Information
Acknowledgments

Special thanks to Jonathan Gibbons of Sun Microsystems, whose feedback helped me improve this article.