Introduction a JShell et API StackWalker : REPL Java 9 et analyse de pile d'appel

Explorez JShell, le REPL Java 9 pour executer du code interactif, et l'API StackWalker pour analyser la pile d'appel de vos applications.

Mahmoud DEVO
Mahmoud DEVO
December 27, 2025 8 min read
Introduction a JShell et API StackWalker : REPL Java 9 et analyse de pile d'appel

Introduction a JShell et API de Stack-Walking

Java 9 a marque un tournant majeur dans l’evolution du langage avec l’introduction de deux fonctionnalites revolutionnaires : JShell et l’API StackWalker. Ces outils repondent a des besoins fondamentaux des developpeurs Java modernes.

Pourquoi JShell est-il Important ?

Avant Java 9, les developpeurs Java etaient contraints de creer un projet complet, ecrire une classe avec une methode main(), compiler et executer le code, meme pour tester une simple expression. Ce processus etait fastidieux compare aux langages interpretes comme Python ou JavaScript qui disposaient deja de REPL (Read-Eval-Print Loop).

JShell comble ce vide en offrant :

  • Experimentation rapide : Testez des idees sans creer de projet
  • Apprentissage facilite : Ideal pour les debutants qui decouvrent Java
  • Prototypage instantane : Validez des algorithmes avant implementation
  • Documentation interactive : Explorez les APIs en temps reel
  • Debugging simplifie : Isolez et testez des portions de code problematiques

Pourquoi l’API StackWalker est-elle Essentielle ?

L’API StackWalker resout plusieurs problemes historiques de Java concernant l’acces a la pile d’appel :

  • Performance optimisee : Contrairement a Thread.getStackTrace() qui capture toute la pile, StackWalker permet un acces lazy et filtre
  • API standard : Remplace les hacks utilisant sun.reflect.Reflection (non portable)
  • Securite renforcee : Controle precis sur les informations exposees
  • Integration Stream API : S’integre parfaitement avec les fonctionnalites Java 8+

Section 155.2 : Entrer et sortir de JShell

Avant de commencer a utiliser JShell, assurez-vous que votre variable d’environnement JAVA_HOME pointe vers une installation JDK 9 ou superieure. Pour lancer JShell, executez simplement la commande suivante :

$ jshell

Si tout se passe bien, vous devriez voir un prompt jshell>. Pour sortir de JShell, utilisez la commande /exit.

Options de Demarrage Utiles

# Demarrer avec mode verbose (affiche le type de chaque expression)
$ jshell -v

# Demarrer avec un classpath personnalise
$ jshell --class-path libs/mylib.jar

# Demarrer avec des modules specifiques
$ jshell --add-modules java.sql

# Demarrer en mode feedback concis
$ jshell --feedback concise

Section 155.3 : Expressions dans JShell

Dans JShell, vous pouvez evaluer des expressions Java avec ou sans point-virgule. Ces expressions peuvent etre simples comme 4+2 ou plus complexes comme System.out.printf("I am %d years old.\n", 421). Les boucles et les conditions fonctionnent egalement :

jshell> for (int i = 0; i<3; i++) {
   ...> System.out.println(i);
   ...> }

Notez que les expressions dans les blocs doivent avoir des point-virgules !

Exemples d’Expressions Courantes

// Expressions mathematiques
jshell> Math.pow(2, 10)
$1 ==> 1024.0

// Manipulation de chaines
jshell> "Hello".repeat(3)
$2 ==> "HelloHelloHello"

// Collections
jshell> var list = List.of(1, 2, 3, 4, 5)
list ==> [1, 2, 3, 4, 5]

jshell> list.stream().filter(n -> n % 2 == 0).toList()
$3 ==> [2, 4]

// Dates
jshell> import java.time.*
jshell> LocalDate.now().plusDays(30)
$4 ==> 2025-01-26

Section 155.4 : Methodes et classes

Dans JShell, vous pouvez definir des methodes et des classes :

jshell> void speak() {
   ...> System.out.println("hello");
   ...> }
jshell> class MyClass {
   ...> void doNothing() {}
   ...> }

Aucun modificateur d’acces n’est necessaire. Comme pour les variables, il est possible de redefinir des methodes et des classes.

Commandes JShell Essentielles

// Lister toutes les variables definies
jshell> /vars

// Lister toutes les methodes definies
jshell> /methods

// Lister toutes les classes definies
jshell> /types

// Afficher l'historique des commandes
jshell> /history

// Editer une methode existante
jshell> /edit myMethod

// Sauvegarder la session dans un fichier
jshell> /save mySession.jsh

// Charger un fichier de session
jshell> /open mySession.jsh

// Lister les imports actifs
jshell> /imports

// Reinitialiser JShell
jshell> /reset

// Afficher l'aide complete
jshell> /help

Exemple de Session Complete

jshell> record Person(String name, int age) {}
|  created record Person

jshell> var persons = List.of(
   ...>     new Person("Alice", 30),
   ...>     new Person("Bob", 25),
   ...>     new Person("Charlie", 35)
   ...> )
persons ==> [Person[name=Alice, age=30], Person[name=Bob, age=25], Person[name=Charlie, age=35]]

jshell> persons.stream()
   ...>     .filter(p -> p.age() > 28)
   ...>     .map(Person::name)
   ...>     .forEach(System.out::println)
Alice
Charlie

jshell> /vars
|    List<Person> persons = [Person[name=Alice, age=30], ...]

Section 156 : API de Stack-Walking

Avant Java 9, l’acces a la pile d’appel etait limite a une classe interne sun.reflect.Reflection. Cette methode est depreciated. Une alternative standard est maintenant disponible via la classe java.lang.StackWalker, concue pour etre efficace en permettant un acces lazy a la pile d’appel.

Options de Configuration StackWalker

// Options disponibles
StackWalker.Option.RETAIN_CLASS_REFERENCE  // Conserver les references de classe
StackWalker.Option.SHOW_REFLECT_FRAMES     // Inclure les frames de reflexion
StackWalker.Option.SHOW_HIDDEN_FRAMES      // Inclure les frames caches (lambda, etc.)

Section 156.1 : Afficher toute la pile d’appel du thread actuel

Voici comment afficher toute la pile d’appel du thread actuel :

package test;

import java.lang.StackWalker.Option;
import java.lang.StackWalker.StackFrame;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.List;
import java.util.stream.Collectors;

public class StackWalkerExample {
  public static void main(String[] args) throws NoSuchMethodException, SecurityException, IllegalAccessException, IllegalArgumentException, InvocationTargetException {
    Method fooMethod = FooHelper.class.getDeclaredMethod("foo", (Class<?>[])null);
    fooMethod.invoke(null, (Object[]) null);
  }
}

class FooHelper {
  protected static void foo() {
    BarHelper.bar();
  }
}

class BarHelper {
  protected static void bar() {
    List<StackFrame> stack = StackWalker.getInstance()
      .walk((s) -> s.collect(Collectors.toList()));
    for(StackFrame frame : stack) {
      System.out.println(frame.getClassName() + " " + frame.getLineNumber() + " " + frame.getMethodName());
    }
  }
}

Section 156.2 : Afficher la classe appelante

Voici comment afficher la classe appelante :

public class StackWalkerExample {
  public static void main(String[] args) {
    FooHelper.foo();
  }
}

class FooHelper {
  protected static void foo() {
    BarHelper.bar();
  }
}

class BarHelper {
  protected static void bar() {
    System.out.println(StackWalker.getInstance(Option.RETAIN_CLASS_REFERENCE).getCallerClass());
  }
}

Section 156.3 : Afficher les frames de reflexion et autres

Il est possible d’inclure des frames de reflexion et autres dans les stack traces en ajoutant certaines options a l’instance de StackWalker. Par exemple, pour inclure les frames de reflexion :

package test;

import java.lang.StackWalker.Option;
import java.lang.StackWalker.StackFrame;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.List;
import java.util.stream.Collectors;

public class StackWalkerExample {
  public static void main(String[] args) throws NoSuchMethodException, SecurityException, IllegalAccessException, IllegalArgumentException, InvocationTargetException {
    Method fooMethod = FooHelper.class.getDeclaredMethod("foo", (Class<?>[])null);
    fooMethod.invoke(null, (Object[]) null);
  }
}

class FooHelper {
  protected static void foo() {
    BarHelper.bar();
  }
}

class BarHelper {
  protected static void bar() {
    // Afficher les frames de reflexion
    List<StackFrame> stack = StackWalker.getInstance(Option.SHOW_REFLECT_FRAMES)
      .walk((s) -> s.collect(Collectors.toList()));
    for(StackFrame frame : stack) {
      System.out.println(frame.getClassName() + " " + frame.getLineNumber() + " " + frame.getMethodName());
    }
  }
}

Cas d’Utilisation Pratiques de StackWalker

1. Logging Ameliore

public class SmartLogger {
    private static final StackWalker walker =
        StackWalker.getInstance(Option.RETAIN_CLASS_REFERENCE);

    public static void log(String message) {
        Class<?> callerClass = walker.getCallerClass();
        String callerMethod = walker.walk(s ->
            s.skip(1).findFirst().map(StackFrame::getMethodName).orElse("unknown")
        );

        System.out.printf("[%s.%s] %s%n",
            callerClass.getSimpleName(), callerMethod, message);
    }
}

// Utilisation
public class MyService {
    public void processOrder() {
        SmartLogger.log("Processing order...");
        // Output: [MyService.processOrder] Processing order...
    }
}

2. Verification de Securite

public class SecurityChecker {
    private static final Set<String> TRUSTED_PACKAGES = Set.of(
        "com.myapp.security",
        "com.myapp.admin"
    );

    private static final StackWalker walker =
        StackWalker.getInstance(Option.RETAIN_CLASS_REFERENCE);

    public static void checkAccess() {
        boolean isTrusted = walker.walk(frames ->
            frames.skip(1)
                  .map(StackFrame::getDeclaringClass)
                  .map(Class::getPackageName)
                  .anyMatch(TRUSTED_PACKAGES::contains)
        );

        if (!isTrusted) {
            throw new SecurityException("Acces refuse depuis un package non autorise");
        }
    }
}

3. Debugging et Profilage

public class MethodProfiler {
    private static final StackWalker walker = StackWalker.getInstance();

    public static void traceMethodEntry() {
        walker.walk(frames -> {
            frames.skip(1).limit(5).forEach(frame -> {
                System.out.printf("  at %s.%s (line %d)%n",
                    frame.getClassName(),
                    frame.getMethodName(),
                    frame.getLineNumber());
            });
            return null;
        });
    }
}

4. Detection de Cycles d’Appels

public class CycleDetector {
    private static final StackWalker walker = StackWalker.getInstance();

    public static void checkForRecursion(int maxDepth) {
        String currentMethod = walker.walk(s ->
            s.skip(1).findFirst().map(StackFrame::getMethodName).orElse("")
        );

        long count = walker.walk(s ->
            s.filter(f -> f.getMethodName().equals(currentMethod)).count()
        );

        if (count > maxDepth) {
            throw new StackOverflowError(
                "Recursion trop profonde detectee pour: " + currentMethod);
        }
    }
}

Bonnes Pratiques

1. Reutilisez les instances de StackWalker

// BON : Instance reutilisable (thread-safe)
private static final StackWalker WALKER =
    StackWalker.getInstance(Option.RETAIN_CLASS_REFERENCE);

// MAUVAIS : Creation d'instance a chaque appel
public void log() {
    StackWalker.getInstance().walk(...); // Evitez ceci
}

2. Limitez la profondeur de parcours

// BON : Limiter aux frames necessaires
walker.walk(s -> s.limit(5).collect(toList()));

// MAUVAIS : Parcourir toute la pile inutilement
walker.walk(s -> s.collect(toList())); // Peut etre couteux

3. Utilisez les bonnes options selon vos besoins

// Pour obtenir la classe appelante
StackWalker.getInstance(Option.RETAIN_CLASS_REFERENCE);

// Pour debugger avec reflexion visible
StackWalker.getInstance(Option.SHOW_REFLECT_FRAMES);

// Combinaison d'options
StackWalker.getInstance(Set.of(
    Option.RETAIN_CLASS_REFERENCE,
    Option.SHOW_HIDDEN_FRAMES
));

4. Preferez forEach a stream().collect() quand possible

// BON : Direct et efficace
walker.forEach(frame -> processFrame(frame));

// MOINS BON : Cree une liste intermediaire
walker.walk(s -> s.collect(toList())).forEach(this::processFrame);

5. Cachez les resultats si la pile ne change pas

// Pour des appels repetes au meme endroit
private String cachedCallerInfo;

public String getCallerInfo() {
    if (cachedCallerInfo == null) {
        cachedCallerInfo = walker.walk(s ->
            s.skip(1).findFirst()
             .map(f -> f.getClassName() + "." + f.getMethodName())
             .orElse("unknown")
        );
    }
    return cachedCallerInfo;
}

Pieges Courants

1. Oublier que skip(1) saute la methode courante

// PIEGE : getCallerClass() retourne la classe qui appelle getCallerClass
// Si vous voulez l'appelant de VOTRE methode :
public void myMethod() {
    // Ceci retourne la classe contenant myMethod, pas son appelant !
    Class<?> wrong = walker.getCallerClass();

    // Correct : skip la frame de myMethod
    Class<?> correct = walker.walk(s ->
        s.skip(1).findFirst()
         .map(StackFrame::getDeclaringClass)
         .orElse(null)
    );
}

2. Ignorer les frames de reflexion dans le decompte

// PIEGE : Les frames de reflexion sont cachees par defaut
// Si vous utilisez Method.invoke(), la pile peut sembler "courte"

// Solution : Utilisez SHOW_REFLECT_FRAMES si vous debuggez
StackWalker.getInstance(Option.SHOW_REFLECT_FRAMES);

3. Ne pas gerer le cas ou la pile est vide

// PIEGE : findFirst() peut retourner Optional.empty()
String caller = walker.walk(s ->
    s.skip(100).findFirst().get().getMethodName() // NoSuchElementException !
);

// CORRECT : Toujours gerer le cas Optional vide
String caller = walker.walk(s ->
    s.skip(100).findFirst()
     .map(StackFrame::getMethodName)
     .orElse("unknown")
);

4. Utiliser StackWalker pour de la logique metier

// PIEGE : La pile d'appel peut changer selon l'optimisation JIT
// Ne basez JAMAIS de logique metier sur la pile

// MAUVAIS
if (walker.getCallerClass().getSimpleName().equals("AdminService")) {
    grantAccess(); // Fragile et dangereux !
}

// CORRECT : Utilisez des mecanismes de securite explicites
if (securityContext.hasRole("ADMIN")) {
    grantAccess();
}

Conclusion

Dans ce tutoriel approfondi, nous avons explore deux fonctionnalites majeures introduites dans Java 9 :

Points Cles JShell

  • REPL complet : Executez du code Java sans projet ni compilation
  • Experimentation rapide : Ideal pour le prototypage et l’apprentissage
  • Commandes puissantes : /vars, /methods, /edit, /save, /open
  • Integration IDE : Disponible aussi dans IntelliJ et Eclipse

Points Cles StackWalker

  • Performance superieure : Acces lazy et filtre a la pile d’appel
  • API moderne : Integration avec Stream API et Optional
  • Options flexibles : Controle precis sur les frames visibles
  • Cas d’usage variees : Logging, securite, debugging, profilage

Recommandations Finales

  1. Adoptez JShell pour tester rapidement des expressions et APIs
  2. Migrez de Thread.getStackTrace() vers StackWalker pour de meilleures performances
  3. Reutilisez les instances de StackWalker (thread-safe)
  4. Limitez la profondeur de parcours pour optimiser
  5. N’utilisez pas StackWalker pour la logique metier ou securite critique

Sujets pour Approfondir

  • Utilisation avancee de JShell avec des scripts .jsh
  • Integration de JShell dans les pipelines CI/CD pour tests rapides
  • StackWalker avec les modules Java (JPMS)
  • Comparaison de performance : StackWalker vs Thread.getStackTrace()
  • Sockets et programmation reseau en Java
Advertisement

In-Article Ad

Dev Mode

Share this article

Mahmoud DEVO

Mahmoud DEVO

Senior Full-Stack Developer

I'm a passionate full-stack developer with 10+ years of experience building scalable web applications. I write about Vue.js, Node.js, PostgreSQL, and modern DevOps practices.

Enjoyed this article?

Subscribe to get more tech content delivered to your inbox.

Related Articles