@NonNull vs @Nonnull

Introduction

En Java, les annotations @NonNull et @Nonnull sont utilisées pour indiquer qu'une variable, un paramètre, un champ ou une valeur de retour ne doit jamais être null. Cependant, il existe plusieurs bibliothèques qui fournissent ces annotations avec des noms similaires mais des comportements et des utilisations différents. Cette confusion peut être source d'erreurs pour les développeurs.

Les Différentes Annotations

1. javax.annotation.Nonnull (JSR-305)

Package : javax.annotation.Nonnull

import javax.annotation.Nonnull;

public class User {
    @Nonnull
    private String name;

    public User(@Nonnull String name) {
        this.name = name;
    }

    @Nonnull
    public String getName() {
        return name;
    }
}

Caractéristiques : - Fait partie de la JSR-305 (Java Specification Request) - Annotation standard proposée mais jamais finalisée - Largement supportée par les outils d'analyse statique (FindBugs, SpotBugs) - Utilisée par de nombreux frameworks

Dépendance Maven :

<dependency>
    <groupId>com.google.code.findbugs</groupId>
    <artifactId>jsr305</artifactId>
    <version>3.0.2</version>
</dependency>

2. org.jetbrains.annotations.NotNull

Package : org.jetbrains.annotations.NotNull

import org.jetbrains.annotations.NotNull;

public class User {
    @NotNull
    private String name;

    public User(@NotNull String name) {
        this.name = name;
    }

    @NotNull
    public String getName() {
        return name;
    }
}

Caractéristiques : - Développée par JetBrains (créateurs d'IntelliJ IDEA) - Excellente intégration avec IntelliJ IDEA - Support pour Kotlin - Génération de vérifications runtime optionnelle

Dépendance Maven :

<dependency>
    <groupId>org.jetbrains</groupId>
    <artifactId>annotations</artifactId>
    <version>24.0.1</version>
</dependency>

3. lombok.NonNull

Package : lombok.NonNull

import lombok.NonNull;

public class User {
    @NonNull
    private String name;

    public User(@NonNull String name) {
        this.name = name;
    }

    @NonNull
    public String getName() {
        return name;
    }
}

Caractéristiques : - Fait partie de Project Lombok - Génère automatiquement des vérifications null à la compilation - Ajoute du code de vérification dans le bytecode - Lève une NullPointerException si null est passé

Dépendance Maven :

<dependency>
    <groupId>org.projectlombok</groupId>
    <artifactId>lombok</artifactId>
    <version>1.18.30</version>
    <scope>provided</scope>
</dependency>

Code généré :

public User(@NonNull String name) {
    if (name == null) {
        throw new NullPointerException("name is marked non-null but is null");
    }
    this.name = name;
}

4. org.eclipse.jdt.annotation.NonNull

Package : org.eclipse.jdt.annotation.NonNull

import org.eclipse.jdt.annotation.NonNull;

public class User {
    @NonNull
    private String name;

    public User(@NonNull String name) {
        this.name = name;
    }

    @NonNull
    public String getName() {
        return name;
    }
}

Caractéristiques : - Développée par Eclipse Foundation - Excellente intégration avec Eclipse IDE - Support pour l'analyse de flux null - Vérifications au moment de la compilation

Dépendance Maven :

<dependency>
    <groupId>org.eclipse.jdt</groupId>
    <artifactId>org.eclipse.jdt.annotation</artifactId>
    <version>2.2.700</version>
</dependency>

5. jakarta.validation.constraints.NotNull

Package : jakarta.validation.constraints.NotNull

import jakarta.validation.constraints.NotNull;

public class User {
    @NotNull
    private String name;

    public User(@NotNull String name) {
        this.name = name;
    }

    @NotNull
    public String getName() {
        return name;
    }
}

Caractéristiques : - Fait partie de Jakarta Bean Validation (anciennement Java EE) - Validation runtime via un validateur - Utilisée principalement pour la validation de beans - Intégration avec Spring Boot et autres frameworks

Dépendance Maven :

<dependency>
    <groupId>jakarta.validation</groupId>
    <artifactId>jakarta.validation-api</artifactId>
    <version>3.0.2</version>
</dependency>

6. androidx.annotation.NonNull (Android)

Package : androidx.annotation.NonNull

import androidx.annotation.NonNull;

public class User {
    @NonNull
    private String name;

    public User(@NonNull String name) {
        this.name = name;
    }

    @NonNull
    public String getName() {
        return name;
    }
}

Caractéristiques : - Spécifique au développement Android - Intégration avec Android Studio - Utilisée par les bibliothèques AndroidX - Support pour Kotlin

Dépendance Gradle :

implementation 'androidx.annotation:annotation:1.7.0'

Tableau Comparatif

Annotation Package Type Vérification Utilisation
@Nonnull javax.annotation Compile-time Outils statiques Générique
@NotNull org.jetbrains.annotations Compile-time IntelliJ IDEA Kotlin/Java
@NonNull lombok Runtime Génération code Lombok
@NonNull org.eclipse.jdt.annotation Compile-time Eclipse Eclipse
@NotNull jakarta.validation.constraints Runtime Validateur Bean Validation
@NonNull androidx.annotation Compile-time Android Studio Android

Quand Utiliser Quelle Annotation ?

Projet Java Standard

import javax.annotation.Nonnull;

public class Service {
    public void process(@Nonnull String data) {
        // Traitement
    }
}
Recommandation : javax.annotation.Nonnull (JSR-305) pour la compatibilité maximale.

Projet avec Lombok

import lombok.NonNull;

public class Service {
    public void process(@NonNull String data) {
        // Vérification automatique générée
    }
}
Recommandation : lombok.NonNull pour les vérifications runtime automatiques.

Projet Android

import androidx.annotation.NonNull;

public class MainActivity extends AppCompatActivity {
    public void updateUI(@NonNull String text) {
        // Mise à jour UI
    }
}
Recommandation : androidx.annotation.NonNull pour la cohérence avec l'écosystème Android.

Projet Spring Boot avec Validation

import jakarta.validation.constraints.NotNull;

public class UserDTO {
    @NotNull
    private String email;

    @NotNull
    private String password;
}
Recommandation : jakarta.validation.constraints.NotNull pour la validation de beans.

Projet Kotlin/Java avec IntelliJ

import org.jetbrains.annotations.NotNull;

public class Service {
    @NotNull
    public String getData() {
        return "data";
    }
}
Recommandation : org.jetbrains.annotations.NotNull pour l'interopérabilité Kotlin.

Problèmes Courants

1. Mélange d'Annotations

❌ Mauvais :

import javax.annotation.Nonnull;
import lombok.NonNull;

public class User {
    @Nonnull  // JSR-305
    private String name;

    public User(@NonNull String name) {  // Lombok
        this.name = name;
    }
}

✅ Bon :

import lombok.NonNull;

public class User {
    @NonNull
    private String name;

    public User(@NonNull String name) {
        this.name = name;
    }
}

2. Annotation Sans Outil de Vérification

Les annotations comme @Nonnull (JSR-305) ne font rien sans outil d'analyse statique :

import javax.annotation.Nonnull;

public class Service {
    public void process(@Nonnull String data) {
        // Aucune vérification runtime !
        // data peut être null si aucun outil ne vérifie
    }
}

Solution : Utiliser un outil comme SpotBugs, ou utiliser Lombok pour les vérifications runtime.

3. Confusion avec @Nullable

import javax.annotation.Nonnull;
import javax.annotation.Nullable;

public class Service {
    @Nonnull
    public String getData(@Nullable String input) {
        if (input == null) {
            return "default";
        }
        return input;
    }
}

Bonnes Pratiques

1. Choisir une Seule Bibliothèque

Standardiser sur une seule bibliothèque d'annotations dans tout le projet :

// Décision d'équipe : utiliser JSR-305
import javax.annotation.Nonnull;
import javax.annotation.Nullable;

public class UserService {
    @Nonnull
    public User findUser(@Nonnull String id) {
        // ...
    }

    @Nullable
    public User findUserOptional(@Nonnull String id) {
        // ...
    }
}

2. Documenter le Choix

Créer un document d'architecture ou un README :

## Annotations Null Safety

Ce projet utilise JSR-305 (`javax.annotation`) pour les annotations de nullité.

- `@Nonnull` : La valeur ne doit jamais être null
- `@Nullable` : La valeur peut être null

Outils de vérification :
- SpotBugs (analyse statique)
- IntelliJ IDEA (warnings)

3. Configurer les Outils

SpotBugs (Maven) :

<plugin>
    <groupId>com.github.spotbugs</groupId>
    <artifactId>spotbugs-maven-plugin</artifactId>
    <version>4.7.3.6</version>
    <configuration>
        <effort>Max</effort>
        <threshold>Low</threshold>
    </configuration>
</plugin>

IntelliJ IDEA : - Settings → Editor → Inspections → Probable bugs → Nullability problems - Activer "Constant conditions & exceptions"

4. Utiliser avec Optional

import javax.annotation.Nonnull;
import java.util.Optional;

public class UserService {
    @Nonnull
    public Optional<User> findUser(@Nonnull String id) {
        // Retourne toujours un Optional, jamais null
        return Optional.ofNullable(repository.findById(id));
    }
}

5. Annotations au Niveau du Package

Définir des valeurs par défaut pour tout un package :

package-info.java :

@NonNullApi
@NonNullFields
package com.example.myapp;

import org.springframework.lang.NonNullApi;
import org.springframework.lang.NonNullFields;

Avec cette configuration, tous les paramètres et champs sont considérés comme non-null par défaut.

Intégration avec les Frameworks

Spring Framework

Spring fournit ses propres annotations :

import org.springframework.lang.NonNull;
import org.springframework.lang.Nullable;

@Service
public class UserService {
    @NonNull
    public User createUser(@NonNull String name, @Nullable String email) {
        // ...
    }
}

Hibernate/JPA

Utiliser @Column(nullable = false) pour la base de données :

import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import javax.annotation.Nonnull;

@Entity
public class User {
    @Nonnull
    @Column(nullable = false)
    private String name;
}

Bean Validation

import jakarta.validation.constraints.NotNull;
import jakarta.validation.constraints.Email;

public class UserDTO {
    @NotNull(message = "Le nom ne peut pas être null")
    private String name;

    @NotNull
    @Email
    private String email;
}

Migration Entre Annotations

De JSR-305 vers JetBrains

# Rechercher et remplacer
find . -type f -name "*.java" -exec sed -i 's/javax.annotation.Nonnull/org.jetbrains.annotations.NotNull/g' {} +

De JSR-305 vers Lombok

find . -type f -name "*.java" -exec sed -i 's/javax.annotation.Nonnull/lombok.NonNull/g' {} +

Attention : Lombok génère du code runtime, ce qui change le comportement !

Null Safety et Kotlin

Kotlin a la null safety intégrée dans le langage :

// Kotlin
fun processUser(name: String) {  // Non-null par défaut
    println(name)
}

fun processOptionalUser(name: String?) {  // Nullable
    println(name ?: "Unknown")
}

Interopérabilité Java-Kotlin :

// Java avec annotations
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;

public class UserService {
    @NotNull
    public String getName() {
        return "John";
    }

    @Nullable
    public String getEmail() {
        return null;
    }
}
// Kotlin comprend les annotations
val service = UserService()
val name: String = service.name  // Non-null
val email: String? = service.email  // Nullable

Outils d'Analyse Statique

SpotBugs

<plugin>
    <groupId>com.github.spotbugs</groupId>
    <artifactId>spotbugs-maven-plugin</artifactId>
    <version>4.7.3.6</version>
    <executions>
        <execution>
            <goals>
                <goal>check</goal>
            </goals>
        </execution>
    </executions>
</plugin>

Checker Framework

Framework avancé pour la vérification de types :

import org.checkerframework.checker.nullness.qual.NonNull;
import org.checkerframework.checker.nullness.qual.Nullable;

public class User {
    @NonNull String name;
    @Nullable String email;
}

NullAway (Uber)

Outil d'analyse rapide développé par Uber :

dependencies {
    annotationProcessor "com.uber.nullaway:nullaway:0.10.14"
    compileOnly "com.google.code.findbugs:jsr305:3.0.2"
}

Conclusion

Le choix entre @NonNull, @Nonnull, @NotNull dépend de plusieurs facteurs :

Recommandations par Contexte

  1. Projet Java génériquejavax.annotation.Nonnull (JSR-305)
  2. Projet avec Lomboklombok.NonNull
  3. Projet Androidandroidx.annotation.NonNull
  4. Projet Spring Bootorg.springframework.lang.NonNull
  5. Validation de beansjakarta.validation.constraints.NotNull
  6. Projet Kotlin/Javaorg.jetbrains.annotations.NotNull

Principes Clés

  • Cohérence : Utiliser une seule bibliothèque dans tout le projet
  • Documentation : Documenter le choix dans le projet
  • Outils : Configurer les outils d'analyse statique
  • Tests : Tester les cas null explicitement
  • Équipe : S'assurer que toute l'équipe comprend l'approche choisie

Ressources