@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
}
}
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
}
}
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
}
}
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;
}
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";
}
}
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
- Projet Java générique →
javax.annotation.Nonnull(JSR-305) - Projet avec Lombok →
lombok.NonNull - Projet Android →
androidx.annotation.NonNull - Projet Spring Boot →
org.springframework.lang.NonNull - Validation de beans →
jakarta.validation.constraints.NotNull - Projet Kotlin/Java →
org.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