Comparison of Ways to Check Preconditions in Java


Google+ Facebook Twitter LinkedIn Dzone Reddit Digg Blogger Hacker News Addthis  

By Markus Sprunck; Revision: 1.2; Status: final; Last Content Change: Jun 21, 2013; 

This article focuses on preconditions which are directly part of the source code. It doesn't cover other approaches which use annotations and aspect oriented programming.

The use of preconditions, postconditions and invariants is extremely helpful to develop maintainable code. Unfortunately, we find preconditions quite seldom in real world applications. 

In some cases you can read correct preconditions in JavaDoc comments. 

The big disadvantage of JavaDoc comments are that:  
  1. they just get outdated in cases the implementation changes and 
  2. most of the developers just don't read them or read them too late. 

Example Code

To compile the code you will need the following libraries:
  • commons-lang3-3.1.jar (315,805 bytes)
  • guava-14.0.1.jar (2,189,117 bytes)
  • spring-core-3.2.2.RELEASE.jar (866,788 bytes)
In the example code you may see four different ways to directly implement preconditions and the use of asserts. I can't recommend asserts to check preconditions, but you may mix asserts with the four other approaches.

// File 01: PreconditionExample.java 

import com.google.common.base.Preconditions;
import org.apache.commons.lang3.Validate;
import org.springframework.util.Assert; 

public class PreconditionExample { 

    public static void fooPlainJavaValidation(String name, int start, int end) {

        // Preconditions
        if (null == name) {
            throw new NullPointerException("Name must not be null");
        } 

        if (start >= end) {
            throw new IllegalArgumentException("Start (" + start 
                + ") must be smaller than end (" + end + ")"); 
        }         

        // Do something here ...
    } 

    public static void fooSpringFrameworkAssert(String name, int start, int end) {

        // Preconditions
        Assert.notNull(name, "Name must not be null");
        Assert.isTrue(start < end, "Start (" + start
                                    + ") must be smaller than end (" + end + ")"); 

        // Do something here ...
    } 

    public static void fooApacheCommonsValidate(String name, int start, int end) {

        // Preconditions
        Validate.notNull(name, "Name must not be null");
        Validate.isTrue(start < end, "Start (%s) must be smaller than end (%s)", start, end); 

        // Do something here ...
    } 

    public static void fooGuavaPreconditions(String name, int start, int end) {

        // Preconditions
        Preconditions.checkNotNull(name, "Name must not be null");
        Preconditions.checkArgument(start < end, "Start (%s) must be smaller than end (%s)", start, end);

        // Do something here ...
    } 

    public static void fooPlainJavaAsserts(String name, int start, int end) {

        // Preconditions
        assert (null != name) : "Name must not be null";
        assert (start < end) : "Start (" + start + ") must be smaller than end (" + end + ")"; 

        // Do something here ...
    } 

    public static void main(String[] args) { 

        // Case 1: fooPlainJavaValidation
        System.out.println("\n==== Case 1: Plain Java Validation ==========================");
        PreconditionExample.fooPlainJavaValidation("abc", 2, 10);
        try {
            PreconditionExample.fooPlainJavaValidation("abc", 12, 10);
        } catch (RuntimeException ex) {
            System.out.println("fooPlainJavaValidation(\"abc\", 12, 10)\n\t" + ex);
        }
        try {
            PreconditionExample.fooPlainJavaValidation(null, 2, 10);
        } catch (RuntimeException ex) {
            System.out.println("fooPlainJavaValidation(null, 2, 10)\n\t" + ex);
        } 

        // Case 2: fooSpringFrameworkAssert
        System.out.println("\n==== Case 2: Spring Framework Assert ========================");
        PreconditionExample.fooSpringFrameworkAssert("abc", 2, 10);
        try {
            PreconditionExample.fooSpringFrameworkAssert("abc", 12, 10);
        } catch (RuntimeException ex) {
            System.out.println("fooSpringFrameworkAssert(\"abc\", 12, 10)\n\t" + ex);
        }
        try {
            PreconditionExample.fooSpringFrameworkAssert(null, 2, 10);
        } catch (RuntimeException ex) {
            System.out.println("fooSpringFrameworkAssert(null, 2, 10)\n\t" + ex);
        } 

        // Case 3: fooApacheCommonsValidate
        System.out.println("\n==== Case 3: Apache Commons Validate ========================");
        PreconditionExample.fooApacheCommonsValidate("abc", 2, 10);
        try {
            PreconditionExample.fooApacheCommonsValidate("abc", 12, 10);
        } catch (RuntimeException ex) {
            System.out.println("fooApacheCommonsValidate(\"abc\", 12, 10)\n\t" + ex);
        }
        try {
            PreconditionExample.fooApacheCommonsValidate(null, 2, 10);
        } catch (RuntimeException ex) {
            System.out.println("fooApacheCommonsValidate(null, 2, 10)\n\t" + ex);
        } 

        // Case 4: fooGuavaPreconditions
        System.out.println("\n==== Case 4: Guava Preconditions ============================");
        PreconditionExample.fooGuavaPreconditions("abc", 2, 10);
        try {
            PreconditionExample.fooGuavaPreconditions("abc", 12, 10);
        } catch (RuntimeException ex) {
            System.out.println("fooGuavaPreconditions(\"abc\", 12, 10)\n\t" + ex);
        }
        try {
            PreconditionExample.fooGuavaPreconditions(null, 2, 10);
        } catch (RuntimeException ex) {
            System.out.println("fooGuavaPreconditions(null, 2, 10)\n\t" + ex);
        }

        // Case 5: fooGuavaPreconditions
        System.out.println("\n==== Case 5: Plain Java Asserts =============================");
        PreconditionExample.fooPlainJavaAsserts("abc", 2, 10);
        try {
            PreconditionExample.fooPlainJavaAsserts("abc", 12, 10);
        } catch (AssertionError ex) {
            System.out.println("fooPlainJavaAsserts(\"abc\", 12, 10)\n\t" + ex);
        }
        try {
            PreconditionExample.fooPlainJavaAsserts(null, 2, 10);
        } catch (AssertionError ex) {
            System.out.println("fooPlainJavaAsserts(null, 2, 10)\n\t" + ex);
        }
    }
} 


Run the code with the correct VM option (-ea) to enable asserts:

Expected Output

==== Case 1: Plain Java Validation ==========================
fooPlainJavaValidation("abc", 12, 10)
       java.lang.IllegalArgumentException: Start (12) must be smaller than end (10)
fooPlainJavaValidation(null, 2, 10)
       java.lang.NullPointerException: Name must not be null 

==== Case 2: Spring Framework Assert ========================
fooSpringFrameworkAssert("abc", 12, 10)
       java.lang.IllegalArgumentException: Start (12) must be smaller than end (10)
fooSpringFrameworkAssert(null, 2, 10)
       java.lang.IllegalArgumentException: Name must not be null 

==== Case 3: Apache Commons Validate ========================
fooApacheCommonsValidate("abc", 12, 10)
       java.lang.IllegalArgumentException: Start (12) must be smaller than end (10)
fooApacheCommonsValidate(null, 2, 10)
       java.lang.NullPointerException: Name must not be null 

==== Case 4: Guava Preconditions ============================
fooGuavaPreconditions("abc", 12, 10)
       java.lang.IllegalArgumentException: Start (12) must be smaller than end (10)
fooGuavaPreconditions(null, 2, 10)
       java.lang.NullPointerException: Name must not be null 

==== Case 5: Plain Java Asserts =============================
fooPlainJavaAsserts("abc", 12, 10)
       java.lang.AssertionError: Start (12) must be smaller than end (10)
fooPlainJavaAsserts(null, 2, 10)
       java.lang.AssertionError: Name must not be null

Summary

Type Description Advantage Disadvantage
Plain Java Validation Check the needed condition and throw
the appropriate exception
- Works always without
  additional external
  libraries
- Needs a lot of code. 
- Message string can not
  use parameters
Guava Preconditions

[2]
The class Preconditions may throw
 - IllegalArgumentException 
 - NullPointerException
 - IllegalStateException
 - IndexOutOfBoundsException

- Message string can use
  parameters
- Works with GWT
 - footprint 2,189,117 bytes
Apache Commons Validate

[3]
The class Validate may throw
 - NullPointerException
 - IllegalArgumentException
 - IndexOutOfBoundsException


- Message string can use
  parameters
footprint 315,805 bytes
 
Spring Framework Assert

[4]
The class Validate may throw 
 - IllegalArgumentException
 - IllegalStateException
- Automatically part of
  applications which use
  Spring framework
 - Message string can
   not use parameters
- Just basic functionality
Plain Java
Asserts

[1]
Don't use assertions to do work that
your application requires for correct
operation in business logic.

If the condition is false, the assert
throws an AssertionError with just
the message text.

Must be enabled during run time
with VM option.
- Good for checks during
  development and for
  documentation purposes
- May be a source of errors
  if business logic is part of
  asserts.

- An assertion failure will
   never throw an
   appropriate exceptions,
   everything is just
   an AssertionError.

- Message string can not
  use parameters.

Recommendations

  • Use Preconditions in your code - this is most of the time better than a lot of comments. but don't use asserts as preconditions
  • Guava Preconditions and Apache Commons Validate class are the best choice and have almost the same functionality (just take what is more convenient in your technology stack)
References
[1] Programming With Assertions
[2] com.google.common.base.Preconditions.java
[3] org.apache.commons.lang3.Validate.java

Find Code on GitHub

Change History

 Revision  Date  Author  Description
 1.0  Apr 4, 2013  Markus Sprunck   first version
 1.1  Apr 8, 2013  Markus Sprunck  source code now on GitHub
 1.2  Jun 21, 2013  Markus Sprunck  some smaller improvements and typos fixed

Google+ Comments

You may press the +1 button to share and/or comment