Skip to main content

The Open Road: javax.annotation

September 11, 2008

{cs.r.title}




Editor's Note: Java SE 5's annotations were one of the most
profound additions to the Java language, offering a means of
providing metadata about your code to be processed by the compiler or
tools, or even at runtime. While many external projects and
products have adopted annotations quickly and enthusiastically, the
JDK itself continues to make only minimal use of annotations, even
in Java SE 6. However, as Elliotte Rusty Harold reports in this
latest installment of The
Open Road
, that's about to change.

Before we begin, here's a brief update on the status of the
OpenJDK 7 project. The
most recent JDK 7 drop is "http://download.java.net/openjdk/jdk7/promoted/b34/">Build
b34
, posted August 28. As with the builds before it since the
last Open Road article ( "http://download.java.net/openjdk/jdk7/promoted/b33/">b33,
"http://download.java.net/openjdk/jdk7/promoted/b32/">b32,
"http://download.java.net/openjdk/jdk7/promoted/b31/">b31, and
"http://download.java.net/openjdk/jdk7/promoted/b30/">b30),
most of the "http://download.java.net/jdk7/changes/jdk7-b34.html">b34 summary
of changes
items are cleared defects rather than new features.
JMX continues to be an area of particular interest in the recent
builds, with b34 adding "http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=5108776">more
reliable event handling
. Build b33 cleared a number of 2D graphics
bugs and added features, including dealing with non-accelerated
screenshots generated with java.awt.Robot, and a
number of bugs involving the mishandling of transformed images. Build b32
cleared a rare Priority 1 bug, a "http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=6732438">failure
building rt.jar with pack200
. Build b31
cleared a number of significant HotSpot bugs, and b30 has a large
number of fixes for the javap disassembler
tool.

In the future, JSRs approved for Java 7 will start making
their way into JDK 7. In this article, Elliotte looks at a Java 7
JSR you can actually start using today: "http://jcp.org/en/jsr/detail?id=305">JSR 305.

Java 5 introduced
annotations
to the language. Since then, several important third-party libraries and frameworks have taken advantage of them to good
effect, including Guice, Hibernate, and JUnit 4. Libraries designed around
annotations are often simpler and easier to work with than the
equivalent libraries that use reflection or naming conventions to
achieve the same result.

However, the core of Java itself has made relatively little use
of annotations. In fact, only three are built into the language as
of Java 6: @Override, @Deprecated, and
@SuppressWarnings. These are certainly useful, but
they hardly exhaust the space of information we may wish to
annotate in our Java programs.

The JSR 305
effort being led by Bill Pugh of the University of Maryland plans
to change this in Java 7, and dramatically increase the number of
annotations used in standard Java code. By adding annotations to
classes, methods, fields, and variables, you'll be able to tell
static and dynamic analysis tools how you think the code is supposed to behave. Then the tools
can warn you when they suspect the code will behave in some other
fashion. This improves on the current situation, where static
analysis tools need to guess not only what the code does, but what
it's supposed to do. Such tools will still generate many false
positives; but they'll find many more true positives—that is,
genuine errors—in annotated code than in non-annotated code.

In general, annotations can be targeted at the compiler, the
runtime, or both. The JSR 305 annotations are primarily intended
for compilers or other tools that read source code. However, they
are usually retained in the byte code for the benefit of static
analysis tools such as FindBugs that inspect byte code rather than
source.

Annotations can be used by the Java environment
itself (compiler and VM) or by third-party tools. The third-party
tools of most interest to JSR 305 are static analysis tools such as

FindBugs and
PMD. However, one of the
promises of annotations is that future compilers will be able to
check more information about the code they're compiling and
issue more accurate warnings. This is unlikely to be the case with
the default javac compiler in JDK 7, but could well
become a feature of future or third-party compilers.

This article explores the new annotations being proposed in the
javax.annotations package. Although these annotations
are being proposed for inclusion as a standard feature of Java 7,
they're all in the javax.annotations package, and
there's nothing in them that in any way depends on any new features
of Java 7. Provided you add one extra JAR file to your classpath,
all of them should work with any codebase compatible with Java 5 or later. In fact, if you like living on the bleeding edge, you can
download all of these annotations from the Subversion
repository
and start using them today. Most
compilers won't recognize them yet, but the latest versions of
FindBugs will, and other static analysis tools will be adding
support soon.

General Language Annotations

These annotations in the javax.annotations package
give the compiler extra hints about the intended usage of methods,
classes, and arguments that it could not determine with certainty
on its own. For example, the compiler doesn't really know whether
passing null to a method is a mistake, even if it
strongly suspects it. However, by adding the @Nonnull
annotation to the argument, the programmer tells the compiler that
yes, passing null really is a mistake, and if code does that then
the build should fail. Conversely, if the programmer adds a
@CheckForNull annotation to the argument, then they're
saying that nulls may be passed, and the method is expected to
handle it.

For example, the compareTo() method in

java.lang.Comparable is a classic example where
passing null is a flat-out error:

public int compareTo(@Nonnull Integer anotherInteger) {
  int thisVal = this.value;
  int anotherVal = anotherInteger.value;
  return (thisVal < anotherVal ? -1 : (thisVal==anotherVal ? 0 : 1));
}

Any caller that passes null to compareTo() deserves
the NullPointerException they get. When
compareTo() throws a

NullPointerException, the bug is in the
calling method.

However, the equals() method is expected to handle
null without throwing a NullPointerException, and if
it does, then the implementer of the equals() method
is at fault. When equals() throws a

NullPointerException, the bug is in the
called method.

public boolean equals(@CheckForNull Object o) {
    if ( o == null) return false;
    if (o instanceof Integer) {
        return value == ((Integer) o).intValue();
    }
    return false;
}

There's also a third possibility, which is that the method
implicitly accepts nulls but doesn't need to check for them. For
instance, this would be the case for a setter method that allows a
property to be set to null. For these methods, you can annotate the
parameter as @Nullable, indicating that null is a
perfectly OK value and no special check is required. It's also the
case for a slightly shorter version of the equals()
method that realizes null is never an instanceof any type.

public boolean equals(@Nullable Object o) {
    if (o instanceof Integer) {
        return value == ((Integer) o).intValue();
    }
    return false;
}

Now, these are two well-known methods with well-defined
contracts. Static analysis tools can implement the correct nullness
checks directly for them. But there are thousands more non-standard
methods for which the null behavior is not known in advance, unless
the code specifies it. When you annotate your methods with
@CheckForNull, @Nonnull, and
@Nullable, static analysis can be performed on all
methods, not just those few of which the tool has advance
knowledge.

I personally don't find it particularly onerous to put simple
@CheckForNull and @Nonnull annotations on
each parameter. Indeed, the hardest part of this is merely figuring
out what the behavior of every method should be when passed a null
argument. However, that's work we should be doing anyway, although
mostly we haven't been. Once these parameters are in place,
your IDE can now warn you of unannotated methods where you may not
have properly considered and documented the behavior in face of
null parameters.

Nonetheless, if you do find it onerous to add an extra annotation
to each method, you can also declare entire methods, classes, and
even packages to be @CheckForNull,
@Nonnull, or @Nullable. This annotation
is then applied to each method in the class or package, and to all
subclasses of those classes. Of course, you can override the
default annotation on individual methods within a class or classes
within a package simply by applying a different annotation. For
example, a new Integer class might be declared like
so:

@Nonnull
public class Integer extends Number {

//...

    public boolean equals(@Nullable Object o) {
        if (o instanceof Integer) {
            return value == ((Integer) o).intValue();
        }
        return false;
    }

//...
}

This often catches more than you'd like, though. Instead, you can
annotate a package, class, or method with
ParametersAreNonnullByDefault to indicate that method
arguments are not null unless the method overrides a superclass
method or another, closer annotation takes precedence.

Preconditions

In my opinion, the biggest syntactic omissions in Java are not
closures or properties or regular expressions or similarly sexy
topics. Rather, they are the much more important but less popular
preconditions and class invariants. Far too much
Java code blithely assumes that arguments are correct without
verifying them. Besides the null annotations already introduced,
JSR-305 brings in several other annotations
for checking a variety of preconditions and invariants:

  • CheckForSigned
  • Signed
  • Nonnegative
  • MatchesPattern
  • Syntax
Signedness Annotations

The CheckForSigned annotation indicates that values
passed may be negative and that the method should consider this
before executing code. For example,

public void setSpeed(@CheckForSigned double speed) {
    if (speed < 0) this.speed = 0;
    this.speed = speed;
}

Here, negative speed is a legal input value but requires special
handling. However, if the caller is responsible for passing in a
non-negative value, then annotate the argument with @Nonnegative

instead:

public void setSpeed(@Nonnegative double speed) {
    if (speed < 0) throw new IllegalArgumentException("Speed cannot be negative.");
    this.speed = speed;
}

Note that non-negative is not the same as positive. Non-negative
numbers include zero, but the positive numbers don't. That is, zero
is not negative, but is not positive either.

Finally, the @Signed annotation indicates that the
value passed in can be of any sign and no special handling is
required:

public void setBalance(@Signed double value) {
    this.value = value;
}
Syntax

You can apply the @Syntax annotation to string
values, arguments, and variables to indicate that the string is
expected to contain a fragment of some well-defined language such
as XML, regular expressions, or JavaScript. For example, here we
require that a local variable/return value contain valid HTML:

@Syntax("HTML")
public String get401Page(String problem) {
    @Syntax("HTML") String s =
       "<html><body><h1>" + problem + "</h1></body></html>");
    return s;
}

The exact syntaxes that are recognized will vary from tool to
tool. There is no one canonical list, though there are several
suggested values:

  • Java
  • RegEx
  • JavaScript
  • Ruby
  • Groovy
  • SQL
  • FormatString

Furthermore, you can specify subtypes of syntax by adding a
colon and a list of key-value pairs, separated by ampersands after the
name. For example, HTML?version=3.2&fragment=true
could indicate that the syntax is version 3.2 of HTML, but only a
document fragment that doesn't necessarily have to begin with
<html> and end with </html>. This offers additional
hints to tools that recognize them. Tools can ignore hints they
don't recognize.

For custom and unusual syntaxes that your tool doesn't
recognize, you can specify a regular expression with
@MatchesPattern instead. For example, consider a
method that extracts the month from dates in the form "2008-12-15."
You could annotate its parameter thusly:

public int readMonth(@MatchesPattern("\\d\\d\\d\\d-\\d\\d-\\d\\d") String date) {
    //...
}

For the truly advanced (or paranoid), you can create custom
validators that run code to check constant values. For example, you
could write a checksum verifier annotation for credit card numbers,
or even one that contacts the credit card company to make sure the
card is still active. Here's simpler definition for a class that
annotates an int as being between 1 and 12; that is, a
month:

@Documented @TypeQualifier @Retention(RetentionPolicy.RUNTIME) 
public @interface Month {

   class Validator implements TypeQualifierValidator<Month> {
      public boolean forConstantValue(Month annotation, Object v) {
        if (v instanceof Integer) {
            Integer i = (Integer) v;
            if (i.intValue() >= 1 && i.intValue() <= 12) {
             return true;
            }
        }
        return false;
     }
   }

}

Proper Usage

Many classes have conditions on how they are used. For instance,
Hibernate-generated classes may require that all the setter methods
be invoked once each before the business logic methods are called.
JUnit 3 test classes assume that a test case's setUp()
method will invoke the superclass's setUp() method
before running their own code.

To my way of thinking, these conventions are broken by design.
Post-construction, clients should be able to invoke any method on
any object at any time in any order. Nonetheless, such usage
conventions are common and almost never sufficiently documented.
Bugs involving incorrect class usage are legion. Consequently, JSR
305 introduces a couple of methods to both document the correct
usage and help tools identify incorrect usage:

  • @OverridingMethodsMustInvokeSuper
  • @CheckReturnValue

Since these are all conventions established by the class rather
than the language, there's no other way a static analysis tool
could possibly detect these errors, short of knowing the
conventions for usage of every single class.

@OverridingMethodsMustInvokeSuper

The @OverridingMethodsMustInvokeSuper is useful
precisely in the JUnit case, when an overriding method must invoke
the superclass's variant. This is declared on the superclass, and
then the programmer can be warned if they forget to override the
method. For example:

@OverridingMethodsMustInvokeSuper
public void setUp() {
}

Unlike most of the annotations discussed here, which can applied
at any level, @OverridingMethodsMustInvokeSuper can
only be attached to methods.

@CheckReturnValue

The @CheckReturnValue value annotation is helpful
in cases where a programmer might suspect a method to change an
object rather than returning a new object. For example, consider
the trim() method in java.lang.String.
This method creates a new String that is the same as the
old String except that white space has been removed from each end.
The original String is not changed at all, nor is anything else.
Thus if one invokes it like so, absolutely nothing happens:

s.trim();

You might as well not have bothered. The correct way to invoke
it is by assigning the result to a variable, possibly the same one
that was used to invoke it:

s = s.trim();

Failing to do this is a very common mistake and a frequent source
of hard-to-find bugs. By placing the @CheckReturnValue
annotation on a method, we clue in the compiler that it should
probably issue a warning if the return value isn't used.

@CheckReturnValue
public String trim() {
//...

I/O

When passing input and output streams to methods, or retrieving
them from methods, it is often hard to tell what the method will do
with the stream. In particular, should you close the stream when
you're done or not? Since some other class has a reference to the
stream, it's hard to tell. The invoking object may or may not use the stream after you're
done with it, and it may or may not close it. The next three
annotations help both document and verify what happens to input and
output streams (or other closeable resources) when a method is done
with it.

  • @WillClose
  • @WillNotClose
  • @WillCloseWhenClosed

If a method will exhaust a stream (or reader or writer) and
close it before returning, then annotate the argument with
@WillClose like so:

public String readString(@WillClose Reader in) throws IOException {
  try {
      StringBuffer s = new StringBuffer();
      for (int i = in.read(); i != -1; i = in.read()) {
        s.append((char) i);
      }
      return s.toString();
  }
  finally {
     in.close();
  }
}

If you don't plan to close the resource, annotate with

@WillNotClose instead:

public String readString(@WillNotClose Reader in) throws IOException {
    StringBuffer s = new StringBuffer();
    for (int i = in.read(); i != -1; i = in.read()) {
      s.append((char) i);
    }
    return s.toString();
}

Finally, you can use @WillCloseWhenClosed to tie two
resources together. This is typically used on a constructor or
factory method argument to promise that when the constructed object
is closed, the resource will be closed too. For example, this
method returns a java.io.Reader that uses UTF-8 to
translate an underlying input stream. When the reader is closed,
the input stream will be too.

public static Reader getUTF8Reader(@WillCloseWhenClosed InputStream in) 
  throws IOException {
    return new BufferedReader(new InputStreamReader(in, "UTF-8"));
}

In fact, this is how input stream readers have always worked,
but now it's more explicit and obvious.

Security

Three annotations are used to indicate security properties, and
are useful for static analysis tools inspecting web applications
for potential vulnerabilities to cross-site scripting, cross-site
request forgery, SQL injection, and similar attacks:

  • @Tainted
  • @Untainted
  • @Detainted

The plan here is to identify which data comes from where. Data
from outside the application is marked as @Tainted and
not to be trusted. Data from inside the application--for instance,
constant strings--is marked as @Untainted and assumed
to be trusted. Tainted data that has been sanitized by passing it
to some sort of escaping function can be annotated as

@Detainted. Security analysis tools can then follow
the path of data through a system and make sure that tainted data
never reaches a part of the system that trusts its input.

Concurrency Annotations

Multithreaded code is some of the hardest to write and debug in
any language, and Java is no exception. This is true when you write
all the code yourself, and triply so when you have to depend on
third-party libraries. Few libraries bother to properly document
their thread-safety characteristics. For instance, off the top of
your head, can you tell me whether a
java.text.DateFormat is or is not thread-safe? And in
more detail, can you tell me under what conditions a
DateFormat object can be used in multiple threads?
What, if anything, do you need to synchronize to make it
thread-safe?

Four annotations in javax.annotations.concurrent
are being proposed to improve this situation:

  • @Immutable
  • @ThreadSafe
  • @NotThreadSafe
  • @GuardedBy
@Immutable is the simplest concurrency annotation.
It merely declares that objects of a class do not change after
construction and are thus immutable. For example,

@Immutable
public class String {
...

Immutable objects are inherently thread-safe. The main benefit
of this annotation is simply documentation for client programmers.
However, static analysis tools can now complain if they spot, for
example, a setter method on a non-final field in a supposedly
immutable class.

@ThreadSafe promises that the class behaves well
even when instances are shared between different threads at the
same time. It has sufficient internal synchronization to avoid
state inconsistency, race conditions, deadlocks, starvation, and
similar problems. For example,

@ThreadSafe
public class StringBuffer {
...

This annotation warns static analysis tools to pay more
attention to thread-safety issues they uncover that might not be the result of an
error in a class that is specifically marked as
@ThreadUnsafe:

@ThreadUnsafe
public class StringBuilder {
...

A static analysis tool will not flag thread-safety errors in
explicitly unsafe classes, but instead will warn @ThreadSafe clients
if they fail to use enough external synchronization mechanisms when
sharing unsafe objects across threads.

Finally @GuardedBy warns clients that they can
access a class safely in multiple threads if and only if they first
obtain a lock on the specified object. For example, here we say you
have to have a lock on the object itself:

@GuardedBy("this")
public class Foo {
...

Some classes require a lock on the class rather than the
object:

@GuardedBy("Foo.class")
public class Foo {
...

Other classes may have a method to return their lock object:

import java.util.concurrent.locks.*;

@GuardedBy("getLock()")
public class Foo {

  private final Lock lock = new ReentrantLock();

  public Lock getLock() {
    return lock;
  }

...

Of course, you still have to design the class such that it is
thread safe when the correct lock is held. However, now it's simpler
to document the proper lock and verify that it's used
correctly.

Summing Up

Code that uses annotations is clearer, more expressive, and
shorter. All of this contributes to more reliable, bug-free code.
JSR 305 promises to help us more clearly and easily say what we
mean and leave less to be inferred or guessed at. That's a big step
forward.

Remember, although these annotations are being proposed for
inclusion as standard in Java 7, they don't depend on any
new features of Java 7. With just one extra JAR file, all of this
should work with any codebase compatible with Java 5 or later. Why wait
for Java 7? Start buttressing your code with JSR 305 annotations
today.


width="1" height="1" border="0" alt=" " />
Elliotte Rusty Harold is the author of numerous books including Java I/O, Java Network Programming, and the upcoming Refactoring HTML.
Related Topics >> JSR   |   Programming   |