Use Java assertions to test your assumptions about program correctness and check them in your code. Credit: Toey Andante/Shutterstock Developers make assumptions about how our code will behave when executed, but we’re not always right. Without certainty, it is challenging to write programs that work correctly at runtime. Java assertions provide a relatively easy way to verify your programming logic is correct. What you’ll learn in this Java tutorial In this article, you’ll learn what assertions are, how to write them, and how to use them in your Java programs: What are Java assertions? How to write an assertion in Java Assertions with preconditions and postconditions The difference between Java assertions and Java exceptions download Get the code Download the source code for examples in this tutorial. Created by Jeff Friesen. What are Java assertions? Before JDK 1.4, developers often used comments to document assumptions about program correctness. But comments don’t actually help us test and debug our assumptions. The compiler ignores comments, so there’s no way to use them for bug detection. Developers also frequently do not update comments when changing code. In JDK 1.4, assertions were introduced as a new mechanism for testing and debugging assumptions about Java code. In essence, assertions are compilable entities that execute at runtime, assuming you’ve enabled them for program testing. You can program assertions to notify you of bugs where the bugs occur, greatly reducing the amount of time you would otherwise spend debugging a failing program. Assertions are used to codify the requirements that render a program correct or not by testing conditions (Boolean expressions) for true values, and notifying the developer when such conditions are false. Using assertions can greatly increase your confidence in the correctness of your code. How to write an assertion in Java Assertions are implemented via the assert statement and java.lang.AssertionError class. This statement begins with the keyword assert and continues with a Boolean expression. It is expressed syntactically as follows: assert BooleanExpr; If BooleanExpr evaluates to true, nothing happens and execution continues. If the expression evaluates to false, however, AssertionError is instantiated and thrown, as demonstrated in Listing 1. Listing 1. Java assertions example 1 public class AssertDemo { public static void main(String[] args) { int x = -1; assert x >= 0; } } The assertion in Listing 1 indicates the developer’s belief that variable x contains a value that is greater than or equal to 0. However, this is clearly not the case; the assert statement’s execution results in a thrown AssertionError. Compile Listing 1 (javac AssertDemo.java) and run it with assertions enabled (java -ea AssertDemo). You should observe the following output: Exception in thread "main" java.lang.AssertionError at AssertDemo.main(AssertDemo.java:6) This message is somewhat cryptic in that it doesn’t identify what caused the AssertionError to be thrown. If you want a more informative message, use the assert statement below: assert BooleanExpr : expr; Here, expr is any expression (including a method invocation) that can return a value—you cannot invoke a method with a void return type. A useful expression is a string literal that describes the reason for failure, as demonstrated in Listing 2. Listing 2. Java assertions example 2 public class AssertDemo { public static void main(String[] args) { int x = -1; assert x >= 0: "x < 0"; } } Compile Listing 2 (javac AssertDemo.java) and run it with assertions enabled (java -ea AssertDemo). This time, you should observe the following slightly expanded output, which includes the reason for the thrown AssertionError: Exception in thread "main" java.lang.AssertionError: x < 0 at AssertDemo.main(AssertDemo.java:6) For either example, running AssertDemo without the -ea (enable assertions) option results in no output. When assertions are not enabled, they are not executed, although they are still present in the class file. Assertions with preconditions and postconditions Assertions test a program’s assumptions by verifying that its various preconditions and postconditions aren’t violated, alerting the developer when a violation occurs: A precondition is a condition that must evaluate to true before the execution of some code sequence. Preconditions ensure that callers keep their contracts with callees. A postcondition is a condition that must evaluate to true after the execution of some code sequence. Postconditions ensure that callees keep their contracts with callers. Writing preconditions You can enforce preconditions on public constructors and methods by making explicit checks and throwing exceptions when necessary. For private helper methods, you can enforce preconditions by specifying assertions. Consider the example in Listing 3. Listing 3. Java assertions example 3 import java.io.FileInputStream; import java.io.InputStream; import java.io.IOException; class PNG { /** * Create a PNG instance, read specified PNG file, and decode * it into suitable structures. * * @param filespec path and name of PNG file to read * * @throws NullPointerException when <code>filespec is * null */ PNG(String filespec) throws IOException { // Enforce preconditions in non-private constructors and // methods. if (filespec == null) throw new NullPointerException("filespec is null"); try (FileInputStream fis = new FileInputStream(filespec)) { readHeader(fis); } } private void readHeader(InputStream is) throws IOException { // Confirm that precondition is satisfied in private // helper methods. assert is != null : "null passed to is"; } } public class AssertDemo { public static void main(String[] args) throws IOException { PNG png = new PNG((args.length == 0) ? null : args[0]); } } The PNG class in Listing 3 is the minimal beginning of a library for reading and decoding PNG image files. The constructor explicitly compares filespec with null, throwing NullPointerException when this parameter contains null. The point is to enforce the precondition that filespec not contain null. It’s not appropriate to specify assert filespec != null; because the precondition mentioned in the constructor’s Javadoc would not (technically) be honored when assertions were disabled. (In fact, it would be honored because FileInputStream() would throw NullPointerException, but you shouldn’t depend on undocumented behavior.) However, assert is appropriate in the context of the private readHeader() helper method, which will be completed eventually to read and decode a PNG file’s 8-byte header. The precondition that is always be passed a non-null value will always hold. Writing postconditions Postconditions are typically specified via assertions, regardless of whether or not the method (or constructor) is public. Consider the example in Listing 4. Listing 4. Java assertions example 4 public class AssertDemo { public static void main(String[] args) { int[] array = { 20, 91, -6, 16, 0, 7, 51, 42, 3, 1 }; sort(array); for (int element: array) System.out.printf("%d ", element); System.out.println(); } private static boolean isSorted(int[] x) { for (int i = 0; i < x.length - 1; i++) if (x[i] > x[i + 1]) return false; return true; } private static void sort(int[] x) { int j, a; // For all integer values except the leftmost value ... for (int i = 1; i < x.length; i++) { // Get integer value a. a = x[i]; // Get index of a. This is the initial insert position, which is // used if a is larger than all values in the sorted section. j = i; // While values exist to the left of a's insert position and the // value immediately to the left of that insert position is // numerically greater than a's value ... while (j > 0 && x[j - 1] > a) { // Shift left value -- x[j - 1] -- one position to its right -- // x[j]. x[j] = x[j - 1]; // Update insert position to shifted value's original position // (one position to the left). j--; } // Insert a at insert position (which is either the initial insert // position or the final insert position), where a is greater than // or equal to all values to its left. x[j] = a; } assert isSorted(x): "array not sorted"; } } Listing 4 presents a sort() helper method that uses the insertion sort algorithm to sort an array of integer values. I’ve used assert to check the postcondition of x being sorted before sort() returns to its caller. The example in Listing 4 demonstrates an important characteristic of assertions, which is that they’re typically expensive to execute. For this reason, assertions are usually disabled in production code. In Listing 4, isSorted() must scan through the entire array, which can be time-consuming in the case of a lengthy array. Assertions vs. exceptions in Java Developers use assertions to document logically impossible situations and detect errors in their programming logic. At runtime, an enabled assertion alerts a developer to a logic error. The developer refactors the source code to fix the logic error and then recompiles this code. Developers use Java’s exception mechanism to respond to non-fatal runtime errors such as running out of memory. Such errors may be caused by environmental factors such as a file not existing, or by poorly written code, such as an attempt to divide by 0. An exception handler is often written to gracefully recover from the error so that the program can continue to run. Assertions are no substitute for exceptions. Unlike exceptions, assertions don’t support error recovery (assertions typically halt program execution immediately—AssertionError isn’t meant to be caught); they are often disabled in production code; and they typically don’t display user-friendly error messages (although this isn’t an issue with assert). It’s important to know when to use exceptions rather than assertions. When to use exceptions Suppose you’ve written a sqrt() method that calculates the square root of its argument. In a non-complex number context, it’s impossible to take the square root of a negative number. Therefore, you use an assertion to fail the method if the argument is negative. Consider the following code fragment: public double sqrt(double x) { assert x >= 0 : "x is negative"; // ... } It’s inappropriate to use an assertion to validate an argument in this public method. An assertion is intended to detect errors in programming logic and not to safeguard a method from erroneous arguments. Besides, if assertions are disabled, there is no way to deal with the problem of a negative argument. It’s better to throw an exception, as follows: public double sqrt(double x) { if (x < 0) throw new IllegalArgumentException("x is negative"); // ... } The developer might choose to have the program handle the illegal argument exception, or simply propagate it out of the program where an error message is displayed by the tool running the program. When they read the error message, the developer can fix whatever code led to the exception. You might have noticed a subtle difference between the assertion and the error-detection logic. The assertion tests x >= 0, whereas the error-detection logic tests x < 0. The assertion is optimistic: We assume that the argument is okay. In contrast, the error-detection logic is pessimistic: We assume that the argument is not okay. Assertions document correct logic, whereas exceptions document incorrect runtime behavior. Conclusion In this tutorial you’ve learned how to use assertions to document correct program logic. You’ve also learned why assertions are no replacement for exceptions, and you’ve seen an example where using an exception would be more effective. Related content feature 14 great preprocessors for developers who love to code Sometimes it seems like the rules of programming are designed to make coding a chore. Here are 14 ways preprocessors can help make software development fun again. By Peter Wayner Nov 18, 2024 10 mins Development Tools Software Development feature Designing the APIs that accidentally power businesses Well-designed APIs, even those often-neglected internal APIs, make developers more productive and businesses more agile. By Jean Yang Nov 18, 2024 6 mins APIs Software Development news Spin 3.0 supports polyglot development using Wasm components Fermyon’s open source framework for building server-side WebAssembly apps allows developers to compose apps from components created with different languages. By Paul Krill Nov 18, 2024 2 mins Microservices Serverless Computing Development Libraries and Frameworks news Go language evolving for future hardware, AI workloads The Go team is working to adapt Go to large multicore systems, the latest hardware instructions, and the needs of developers of large-scale AI systems. By Paul Krill Nov 15, 2024 3 mins Google Go Generative AI Programming Languages Resources Videos