Search code examples
javalombokfunctional-interface

Does Lombok @Nonnull also have an effect for e.g. suppliers?


For the following declaration

public static <E extends SomeInterface> E methodName(
    @NonNull Supplier<@NonNull E> supplier,
    Consumer<@NonNull E> consumer)

does Lombok automatically null-check the suppliers result ? And/or does it make it make sense for the consumer to ensure that the E passed in is null-checked (e.g. by generating a null wrapper)?


Solution

  • As per the docs, @NonNull is purely documentary, except for 2 cases:

    • If directly on a parameter (and not on some generics inside a parameter), lombok checks if you have an explicit nullcheck for that parameter at the start of the method somewhere. If you don't, lombok adds one.
    • If directly on a field (and not on some generics inside a field's type), any constructors, setters, and withers lombok generates will add a null check for that value.

    That is it. Lombok does not generate any checks if you stick it on a method / on a return type, for example.

    This is intentional, and any PRs to change this behaviour would be denied. (SOURCE: I'm one of the 2 people who makes the final call on such things).

    To explain specifically why lombok does not and in fact cannot do anything meaningful to @NonNull within <>:

    Imagine you have a method taking an Iterable<String>.

    What do you expect lombok to do? Call .iterator() on the parameter, loop through to the end (keep iterating until hasNext() returns false), and report if any .next() call returns null? This:

    • requires knowing how Iterable works. Lombok cannot encode the sum total knowledge of the entire java ecosystem. At best we can hardcode that we 'know' what the generics mean for well known types and just give up on anything else. And then, I guess, write some sort of plugin system so you can check your own stuff? This is a herculean and complicated affair for.. what, exactly?

    • Simply does not work here - you can make iterables that are literally infinite. Lombok would encode an endless loop, in a way that isn't obvious. Obviously highly undesired behaviour.

    • Literally mathematically impossible. Imagine a Supplier<@NonNull String>. Do we just call it 50 times, check for null, and consider 'good enough' after 50 loops? What about a IntFunction<@NonNull String>. Do we invoke it with all 4 billion possible int values? What about Function<String, @NonNull String>? What if this function isn't 'stable'? The docs of Function do not require that it is - what if invoking it with some value the first time produces a different result from the second time?

    At best lombok can add null checks when you invoke your lambdas inside that method. But this doesn't actually prove much. What if you hand this lambda off to another method? For 'normal' nullity annotations (directly on a parameter), the @NonNull guarantees it's right and this guarantee survives passing the variable around. This simply isn't possible with generics.