Which @Notnull Java Annotation Should I Use

Using NotNull Annotation in method argument

@Nullable and @NotNull do nothing on their own. They are supposed to act as Documentation tools.

The @Nullable Annotation reminds you about the necessity to introduce an NPE check when:

  1. Calling methods that can return null.
  2. Dereferencing variables (fields, local variables, parameters) that can be null.

The @NotNull Annotation is, actually, an explicit contract declaring the following:

  1. A method should not return null.
  2. A variable (like fields, local variables, and parameters) cannot should not hold null value.

For example, instead of writing:

/**
* @param aX should not be null
*/
public void setX(final Object aX ) {
// some code
}

You can use:

public void setX(@NotNull final Object aX ) {
// some code
}

Additionally, @NotNull is often checked by ConstraintValidators (eg. in spring and hibernate).

The @NotNull annotation doesn't do any validation on its own because the annotation definition does not provide any ConstraintValidator type reference.

For more info see:

  1. Bean validation
  2. NotNull.java
  3. Constraint.java
  4. ConstraintValidator.java

Should I use @NotNull and @NonNull together?

There are at least 4 completely different implications of what a nonnull annotation does. Most annotations only imply one of these different things. Therefore, yes, it can make sense to apply more than one such annotation. Unfortunately, the actual meanings of these annotations are quite muddied, because in my experience virtually no developers have considered the fact that they mean different things; in fact, most developers I've met are completely oblivious to the fact that nullity annotations are incredibly complicated and vaguely defined.

The type system interpretation ('cannot be!')

This interpretation is a type assertion that is trying to be as strong as the String in String x; is: It is impossible for that x variable to reference an object that isn't a string, and because it is impossible, naturally, it would be a completely useless idea to check this. Given that declaration, something like String z = (String) x is so useless it's a compiler warning, in fact.

And so it is with such an annotation. Examples of this are checkerframework, eclipse, and intellij's: org.checkerframeworkchecker.nullness.qual.NonNull, org.jetbrains.annotations.NotNull and eclipse's org.eclipse.jdt.annotation.NonNull all primarily imply this meaning (although the intellij one applies to params and methods, whereas checkerframework and eclipse are type_use annotations. I told you this is complicated :P).

These annotations, by implying 'cannot be', are used for write-time checking. For the same reason writing String x = 5; is an immediate, as you write that code and before you even save the file, red wavy underline in your IDE, so is writing, say, someMethod(map.get(key)), where someMethod's parameter is annotated with one of these NonNull annotations. It's not so much a " you shouldn't", it is a "you cannot do that; I won't even let you".

Of course, javac the compiler doesn't actually work that way, it's the IDEs and tools that are trying to make it look like that. Just like you would never cast a variable of type String to String, you actually don't consider such nullity annotations as implying that you should check. No; the mean: It isn't. It implies the check's already done and you don't need to check again.

It's complicated, of course: To dance around the fact that whether a java compiler will in fact stop you from breaking the implication of these nonnull annotations... some tools (such as kotlinc) will inject explicit nullchecks anyway. A bit like how generics can cause javac to inject some typechecks in places you never wrote an explicit cast, to work around the fact that generics are a compile-time bolt-on to retain backwards compatibility. The idea is to consider these implementation details you shouldn't think about.

The DB interpretation

When hibernate uses a class as a template in order to produce a CREATE TABLE SQL statement, it's useful to be able to use annotations to set up constraints and rules and the like. For the same reason you might want to mark a field as: "When turning this into an SQL column, tell the DB engine to put a unique index on this", you may want to mark it as: "When turning this into an SQL column, tell the DB engine to put a non-null constraint on it".

This is as related to the type system interpretation as guns and grandmas: You can have objects that represent rows in a DB but which wouldn't successfully save due to breaking a constraint all the time; for example, any auto-counting unid field usually is 0, and it wouldn't save like that (the DB engine upgrades that 0 into a 'nevermind that; insert without it and let the DB engine's sequence fill in this number). So it is with these: It really doesn't mean anything to java at all; the SQL engine will take care of indicating that the insert/update fails due to a failing constraint. Of course, to save a roundtrip to the DB and because this just isn't allowed to be simple, most DB frameworks will nullcheck as you call the equivalent of .save() or .store(). But that's just a shortcut to that DB constraint.

The validation interpretation

Sometimes objects in java represent an external thing. Such as a DB row (overlapping with the previous meaning somewhat), or, say, a web form as submitted by a user. Such objects SHOULD represent precisely the actual state of the actual external thing it represents. Warts and invalid gobbledygook and all.

And usually you have a framework that lets you validate these. That's what validation nonnull would mean: This field can be null, and if you try to set it to null, no exceptions will occur (hence, they can be). However, as long as that field remains null, if you ask me if the object is valid, the answer is: No.

The lombok interpretation

Unfortunately, lombok muddies the waters somewhat.. just like all the other frameworks do. What lombok's @NonNull means depends on where it shows up. If on a parameter, it means: Lombok, please generate an explicit nullcheck as first line in the method, unless I wrote it already myself.

On fields, it means: "For the sake of @RequiredArgsConstructor, consider this field required; nullcheck it (throwing an exception) in the generated constructor. Also, copy the nullity annotation where relevant, and therefore, if a setter is made for this field.. add that nullcheck inside."

Your specific case

Given that nullity annotations mean completely different things, it is therefore potentially not crazy to apply more than one such annotation to the same construct in your code. If you want the DB engine to generate an SQL NON NULL constraint if this class is used as template, and for any object that represents a row in this table to immediately reject any attempt to put it in an invalid state, adding both can make sense. If you prefer that it is acceptable for objects to represent invalid state, which is almost always the case if the general principle is to construct a blank object and then set each field value one at a time with a setter – then don't add lombok's annotation.

Surely you've covered all complexity, right?

Not even a glimmer. For a real brain twister, consider checkerframework's @PolyNull, and consider that this is an oversimplification of what a proper typesystem ought to truly be able to handle!

DISCLAIMER: I'm a core contributor for Project Lombok.

@NotNull javax annotation vs Intellij @NotNull annotation

i believe you are mixing things:

  1. intellij annotations are to help the developer on spotting probable bugs. If you say that a parameter is Nullable for example, it will warn you if you are not protecting your code against NullPointerExceptions. This can be done compile-time (not related to runtime).
  2. javax validation annotations instead, are about adding validations to fields and throw errors if not met. it's about your business, not about bugs in your code; there might be no bug at all. For example, they can be used to verify that external input matches a specific criteria. Let's suppose you implement an API in your application to be called from the outside. If you add NotNull annotation to a field, if the API caller does not set that field, it will receive an error. Your application is perfectly fine, no bug at all. Obviously, these validations are about the data and done on runtime.

When to use @NotNull and @Nullable IntelliJ annotations?

The @Nullable and @NotNull annotations are used to indicate the IDE that something (argument, attribute, etc) can (or cannot) be null. This way it helps you to detect possibly incorrect code.

This is not a "must follow" rule, but another tool to help the developer in coding a more robust and less error-prone code while using the IDE.

If you're coding alone, the team is small, you're working in a small project or any similar situation... and you feel comfortable without it, then don't use it as it is true the code becomes somehow verbose. This doesn't mean this is not useful for any of the previous situations (it can actually be very helpful too).

On the other hand, if you think you need an extra tool to help you detect possibly failing code against not "nullable" values, or, for instance, you're coding an API to be used by a third party and want to use this annotation instead of several asserts inside the code block... then go for it.

It is all about evaluating pros and cons in the project where you might apply these annotations and decide whether this could give you more benefits than the "problems" it can cause.

Should we always use @NotNull or @Nullable?

In Java primitive types such as int can never be null, this is enforced by the compiler, so the annotations in your code are completely unnecessary.

Even if you managed to pass an Integer as a parameter (via auto-unboxing), a null value won't be allowed in most cases:

add(1, (Integer) null);
=> Null pointer access: This expression of type Integer is null but requires auto-unboxing.

But if a null does manage to be passed as an argument (say, if one of the arguments is a null attribute), the annotations won't prevent it, causing a runtime NullPointerException. If you're so concerned about passing null values (which you shouldn't), you can use Optional to gracefully handle the case when one of the arguments is null:

public static Optional<Integer> add(Integer a, Integer b) {
if (a == null || b == null)
return Optional.empty();
return Optional.of(a + b);
}

Now it'll be the caller's responsibility to determine what to do if the value couldn't be computed, with the added benefit that the caller now is aware that this could happen.



Related Topics



Leave a reply



Submit