JetBrains' @Contract annotation

27,404

Solution 1

First off, I should say that this annotation is only for IDEA to use to check for possible errors. The Java compiler will ignore it almost entirely (it'll be in the compiled artifact but have no effect). Having said that...

The goal of the annotation is to describe a contract that the method will obey, which helps IDEA catch problems in methods that may call this method. The contract in question is a set of semi-colon separated clauses, each of which describes an input and an output that is guaranteed to happen. Cause and effect are separated by ->, and describe the case that when you provide X to the method, Y will always result. The input is described as a comma-separated list, describing the case of multiple inputs.

Possible inputs are _ (any value), null, !null (not-null), false and true, and possible outputs adds fail to this list.

So for example, null -> false means that, provided a null input, a false boolean is the result. null -> false; !null -> true expands on this to say that null will always return false and a non-null value will always return true, etc. Finally, null -> fail means the method will throw an exception if you pass it a null value.

For a multiple-argument example, null, !null -> fail means that, in a two-argument method, if the first argument is null and the second is not null, an exception will be thrown, guaranteed.

If the method does not change the state of the object, but just returns a new value, then you should set pure to true.

Solution 2

The official documentation specifies the formal grammar of all supported and recognized values for the annotation.

In layman's terms:

  • A contract can have 1 or more clauses associated with it
  • A clause is always [args] -> [effect]
  • Args are 1 or more constraints, which are defined as any | null | !null | false | true
  • Effects are only one constraint or fail

Let's run through a quick example - one of my favorites is, "Whatever you pass into this method, it will throw an exception."

@Contract("_-> fail")
public void alwaysBreak(Object o) {
    throw new IllegalArgumentException();
}

Here, we're using _, or "any", to indicate that regardless of what we pass into this method, we're going to throw an exception.

What if we lied and said that this method was going to return true unconditionally?

@Contract("_-> true")
public void alwaysBreak(Object o) {
    throw new IllegalArgumentException();
}

IntelliJ raises some warnings about it.

enter image description here

It's also (obviously) upset that we said we were returning a boolean when we're in a void method...

enter image description here


The main times you'll find yourself wanting to use @Contract is when:

  • You want to guarantee that you return true or false
  • You want to guarantee that you return a non-null value given constraints
  • You want to make clear that you can return a null value given constraints
  • You want to make clear that you will throw an exception given constraints

That's not to say that @Contract is perfect; far from it. It can't do very deep analysis in certain contexts, but having this in your code base allows your tooling to do this sort of analysis for free.

Solution 3

How does the org.jetbrains.annotations.Contract annotation work?

Although the previous answers are informative, I don't feel they address the operative word "work" in your question. That is, they don't explain what IntelliJ is doing behind the scenes to implement their annotations so that you could build your own easily from scratch.

If you glance at the source code below, you might think that it seems a bit overly complicated (or at least verbose) for something as simple-sounding as @NotNull. I would agree with you, and it's one reason I generally avoid @Contract-like clauses that aren't "plain and simple" (like @NotNull) and instead JavaDoc my prereqs directly.

I generally discourage using complex contract annotations—despite the hate I might receive for skipping this trendy new train—and here some reasons why:

  • Annotations can be complex—e.g. having multiple nested annotations in their own definitions and/or a "grammar" with the appearance of Turing completeness. This can lead to a false sense of confidence at compile-time by masking the true culprit of a bug behind layers of obscurity/abstraction and failing to generate the originally intended warnings.
  • Similar but different than my previous point, annotations often hide copious logic from the developer in a handful of keywords, producing difficult to understand code for humans and/or unusual behavior that can be difficult to debug.
  • App configuration is often seen masquerading as annotations. Have a look at the Spring framework.
  • The syntax for defining contracts is by-and-large (IMHO) quite ugly and Makefile-ish. For example, take a glance at some of the JetBrains annotation definitions and supporting files scattered across its repo. Notice the numerous XML files and copious self-reference? I'd hardly call that fun to write and support, especially considering the constantly evolving nature of annotations headed by the back-and-forth between Android and the larger Java community.

Some questions to consider:

  • Is it going too far when an annotation's source code approaches the complexity of the very source it annotates?
  • Is the code segment below really that much better then just checking for null at runtime and logging exceptions with stacktraces? Including something like that forces your users to read through, comprehend, and possibly bug-fix another set of dependencies that define your annotations.

Borrowed from yet another lengthy post about contract semantics that I would argue only further serves my point:

import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import javax.annotation.Nonnull;
import javax.annotation.meta.TypeQualifierDefault;

/**
 * This annotation can be applied to a package, class or method to indicate that the class fields,
 * method return types and parameters in that element are not null by default unless there is: <ul>
 * <li>An explicit nullness annotation <li>The method overrides a method in a superclass (in which
 * case the annotation of the corresponding parameter in the superclass applies) <li> there is a
 * default parameter annotation applied to a more tightly nested element. </ul>
 * <p/>
 * @see https://stackoverflow.com/a/9256595/14731
 */
@Documented
@Nonnull
@TypeQualifierDefault(
{
    ElementType.ANNOTATION_TYPE,
    ElementType.CONSTRUCTOR,
    ElementType.FIELD,
    ElementType.LOCAL_VARIABLE,
    ElementType.METHOD,
    ElementType.PACKAGE,
    ElementType.PARAMETER,
    ElementType.TYPE
})
@Retention(RetentionPolicy.RUNTIME)
public @interface NotNullByDefault
{
}

When and what contracts should you use?

I suggest sticking to the kind whose intentions are crystal-clear from their name, and avoid those with their own set of semantics and language-like definitions.

An example of one to use—in spite of the previous segment—is @NotNull, but keep it limited to when all object parameters must be null.

An example of the sort to avoid are those like Android and IntelliJ's @Contract(...). While I do love their IDEs, the details of their annotations are quite complicated and ultimately turned into a source of more problems and platform incompatibility for me to track down (admittedly caused by my own ignorance in authoring new contracts almost 100% of the time, but why is it so difficult to get it right?)

Summary / Conclusion

Annotations are a great idea clearly generated by programmers looking to "codify" their documentation. I feel they've gone too far lately, turning documentation into semantics-bearing code that leads to some serious conundrums and awkward situations. Even worse, they sometimes endow a false sense of compile-time safety by failing to detect issues manifest in their own implementations. Stick to the very simple and avoid anything that looks like a language that isn't Java (which is what you intended to be writing in the first place).

Further Reading

This brief list is a mix of mostly critical (w/optimism!) sources from both StackOverflow and the web that I feel illustrate some of my points.

In no particular order:

And after all that, I just realized I may still have failed to address your original question in its entirety :)

Share:
27,404
spongebob
Author by

spongebob

Updated on July 08, 2022

Comments

  • spongebob
    spongebob almost 2 years

    How does the org.jetbrains.annotations.Contract annotation work? How does IntelliJ IDEA support it?