Pièges classiques¶
Les erreurs qui piègent le plus souvent quelqu'un qui vient de Rust, C++ ou Python, classées par symptôme. Chaque entrée renvoie au chapitre qui l'explique en détail.
Le bon réflexe
Quand ça ne marche pas : lis le premier Caused by: de la pile d'appels (pas la dernière ligne), puis les logs de démarrage. Avec Spring, la cause réelle est souvent tout en bas d'une longue trace.
Java : égalité et valeurs¶
==renvoiefalsesur deux chaînes ou deuxIntegeridentiques==compare les références pour les objets. Utilisea.equals(b), ouObjects.equals(a, b)siapeut êtrenull. Avec lesInteger,==« marche » entre -128 et 127 (cache) puis échoue au-delà : un bug qui passe les tests. → Mémoire et références- Un objet ajouté à un
HashSetou utilisé comme clé deHashMapest introuvable equalsredéfini sanshashCode(les deux vont ensemble). Unrecordles génère pour toi. → Classes et objets- Un calcul d'entier donne un résultat négatif absurde
- Le dépassement est silencieux (pas de panique comme en Rust en debug), et il n'existe pas d'entiers non signés. Utilise
long, ouMath.addExact/multiplyExactpour lever une exception. → Syntaxe et types NullPointerExceptionsur une ligne sans appel de méthode- Un
Integer,LongouBooleanvalantnullest déballé automatiquement vers un primitif (int x = map.get(cle);). → Génériques et collections
Java : mutabilité et collections¶
- Un objet change « tout seul »
- Deux variables pointent vers le même objet (pas de borrow checker pour l'interdire).
finalfige la variable, pas l'objet. Unrecordqui reçoit uneListgarde la même référence : faisList.copyOf(...)dans son constructeur compact. → Mémoire et références, Records, enums, sealed UnsupportedOperationExceptionsur unaddList.of(...),Map.of(...)etstream.toList()renvoient des collections immuables. Copie dans unenew ArrayList<>(...)pour modifier. → Génériques et collections, Lambdas et streamsConcurrentModificationExceptiondans une boucle- Tu modifies une collection pendant que tu la parcours avec un
for (x : liste). Utiliseliste.removeIf(...)ou un itérateur explicite. → Génériques et collections liste.remove(1)retire le mauvais élément- Sur une
List<Integer>,remove(1)retire l'élément d'indice 1. Pour retirer la valeur 1 :remove(Integer.valueOf(1)). → Génériques et collections - Une liste accepte n'importe quel type
var l = new ArrayList<>();donne uneArrayList<Object>. Avecvar, écris le type dans les chevrons. → Génériques et collections
Java : méthodes, classes, exceptions¶
- Impossible de donner une valeur par défaut à un paramètre
- Java n'a ni valeurs par défaut ni arguments nommés : on écrit des surcharges (une méthode qui appelle l'autre), ou un builder quand il y a beaucoup d'options. → Syntaxe et types
- Un champ vaut
nullalors que le constructeur l'initialise - Le constructeur parent appelle une méthode redéfinie par la sous-classe, avant que les champs de celle-ci soient initialisés. N'appelle pas de méthode redéfinissable dans un constructeur. → Classes et objets
- Une classe d'un autre fichier accède à un champ « protégé »
- Sans mot-clé, la visibilité est « paquet » (pas
private), etprotectedinclut tout le paquet. Mets les champs enprivate. → Classes et objets - Une méthode de secours est exécutée alors que l'
Optionalest rempli orElse(calcul())évalue toujours son argument. UtiliseorElseGet(() -> calcul()). → Exceptions et Optional- Une exception disparaît sans laisser de trace
- Un
catchvide, ou unreturndans unfinally, qui écrase l'exception. Relance-la (en l'enveloppant avec sa cause) ou journalise-la. → Exceptions et Optional - Un compteur partagé entre threads perd des incréments
- Les data races compilent en Java (pas de
Send/Sync). UtiliseAtomicInteger,ConcurrentHashMapousynchronized, ou mieux : pas d'état mutable partagé. → Threads et concurrence getAnnotation(...)renvoienullsur ta propre annotation- Il manque
@Retention(RetentionPolicy.RUNTIME): par défaut l'annotation n'existe plus à l'exécution. → Annotations et réflexion
Outillage¶
java -versionetjavac -versionne donnent pas la même version- Plusieurs JDK installés et un
JAVA_HOMEou unPATHqui pointe vers le mauvais. Maven utiliseJAVA_HOME. → Installer et lancer ./mvnw: Permission denied- Le bit d'exécution a été perdu (archive extraite sans les droits) :
chmod +x mvnw. → Maven - Le code marche avec
java Fichier.javamais pas avec Maven - Le lanceur de fichier source est plus tolérant que
javacsur l'organisation des fichiers et des paquets. → Installer et lancer
Spring Boot¶
- Un contrôleur ou un service est ignoré (route en 404, bean introuvable)
- La classe n'est pas dans le paquet de la classe
@SpringBootApplicationni dans un sous-paquet : le scan des composants ne la voit pas. → Premier projet 404alors que la route existe, logs qui parlent d'une vue@Controllerau lieu de@RestController: Spring cherche une page HTML portant le nom de ta valeur de retour. → API REST- Les contraintes
@NotBlank,@Size... sont ignorées - Il manque
@Validdevant le@RequestBody. Sans lui, personne ne lit ces annotations. → API REST - Des utilisateurs voient les données d'autres utilisateurs
- Un champ mutable dans un bean : les beans sont des singletons partagés par tous les threads de requêtes. Garde l'état dans les variables locales, la base, ou des objets dédiés à la requête. → Injection de dépendances
@Transactional(ou@Cacheable,@Async...) n'a aucun effet- La méthode est appelée depuis la même classe (
this.methode()), ce qui contourne le proxy ; ou elle estprivate. Appelle-la depuis un autre bean. → Annotations et réflexion, Injection de dépendances - Une erreur n'annule pas la transaction
- Par défaut, seul un rollback sur exception unchecked (
RuntimeException) est fait. Pour une exception checked :@Transactional(rollbackFor = ...), ou utilise des exceptions unchecked. → Base de données (JPA) LazyInitializationException: ... (no session)- Une relation chargée paresseusement est lue après la fin de la transaction (souvent en sérialisant une entité en JSON). Charge ce qu'il faut dans le service et renvoie des DTO. → Base de données (JPA)
- Le schéma de prod a dérivé, ou Flyway refuse de démarrer
ddl-auto=updateen production, ou une migration déjà appliquée a été modifiée (somme de contrôle différente). Une modification de schéma = un nouveau fichierV<n>__...sql. → Base de données (JPA)- Hibernate refuse un
recordcomme entité - Une entité doit être une classe avec un constructeur sans argument et des champs modifiables. Les
recordservent aux DTO. → Base de données (JPA) - Après avoir déclaré ton propre bean, un comportement par défaut a changé
- Déclarer un bean (un
JsonMapper, par exemple) désactive celui de Boot et ses réglages. Préfère les customizers ou les propriétés. → Configuration 500avec « parameter name information not available via reflection »- Le code n'a pas été compilé avec
-parameters(IDE qui n'utilise pas lepom.xml). Compile avec Maven, ou nomme le paramètre :@RequestParam("name"). → Premier projet @MockBeanoujavax.persistenceintrouvables- Exemple écrit pour une ancienne version :
@MockitoBeandepuis Boot 3.4 (et seul disponible en Boot 4),jakarta.*depuis Boot 3. → Tests, Ressources UnnecessaryStubbingExceptiondans un test- Un
when(...)n'est jamais utilisé : Mockito est strict par défaut. Retire-le. → Tests - L'appli ne se connecte pas à PostgreSQL en production
- L'URL fournie par Dokploy (
postgresql://...) n'est pas une URL JDBC : il fautjdbc:postgresql://hôte:5432/base, avec l'utilisateur et le mot de passe dans des variables séparées. → Déployer - Tout
/actuatorest visible publiquement - N'expose que
healthetinfo:env,beansouheapdumprévèlent la configuration, voire des secrets. → Déployer