Le projet en question est composé de plusieurs Stateless SessionBeans ayant chacun leur business code. Pour faire simple, l'exemple ci-dessous utiliseras 3 SessionBeans :

  1. Processor : contient une méthode qui lance une exception
  2. Other : contient une méthode qui renvoie une bête chaîne de caractère
  3. Test : utilise les deux Beans précédent pour générer un message

Juste pour le rappel, un SessionBeans est un Enterprise Java Bean (EJB). Un EJB est composé d'une interface (annotée @Local ou @Remote) et d'une classe (annotée @Stateful ou @Stateless) implémentant ladite interface. Voici le code de mes 3 SessionBeans :

1) EJB Processor :

@Local
public interface ProcessorLocal
{
   public String process();
}
@Stateless
public class ProcessorBean implements ProcessorLocal
{ 
   public String process() 
   { 
      throw new RuntimeException("error"); 
   } 
}

2) EJB Other :

@Local
public interface OtherLocal
{
   public String hello();
}
@Stateless
public class OtherBean  implements OtherLocal
{ 
   public String hello() 
   { 
     return "processed by OtherBean"; 
   } 
}

3) EJB Test :

@Remote
public interface TestRemote
{
   public String call();
}
@Stateless
public class TestBean
{
    @EJB
    private ProcessorLocal processor;
 
    @EJB
    private OtherLocal other;
 
    public String call()
    {
          String result = null;
          try
          {
              result = processor.process();
          }
          catch(Exception e)
          {
              result = other.hello();
          }
 
          return result;
    }
}

La méthode call() de cette dernière classe est celle qui est appelée par le WebService pour effectuer la procédure. Elle utilise l'injection JNDI grâce à l'annotation @EJB pour les deux autres SessionBeans. Cela signifie que c'est le serveur qui va s'occuper de créer les instances de classe nécessaires et les référencer dans mon code.

A première vue, l'exception est bien rattrapée et le code devrait renvoyer 'processed by OtherBean'... Et à l'exécution on obtient :

INFO [com.sun.ejb.containers.BaseContainer.postInvoke] ejb.some_unmapped_exception
INFO [com.sun.ejb.containers.BaseContainer.postInvoke] 
SEVERE [com.sun.xml.ws.server.sei.EndpointMethodHandler.invoke] nested exception is: java.rmi.ServerException: RemoteException occurred in server thread; nested exception is: 
        java.rmi.RemoteException: null; nested exception is: 
        javax.ejb.TransactionRolledbackLocalException: Client's transaction aborted

Ce qui se passe est très bizarre et compliqué à identifier : lorsque la RuntimeException est lancée, toutes les références @EJB sont automatiquement cassées ! Et donc l'erreur renvoyée est due au code dans le catch puisque le serveur ne reconnait plus l'instance 'other'. Et cela indépendamment ou se trouve le code (après le catch, dans un finalize, ...). Par contre, sans accéder aux EJB, aucun problème :

try
{
     result = processor.process();
}
catch(Exception e)
{
     result = "failed";
}

En poussant l'expérience un peu plus loin, je me suis rendu compte que cela ne se produisait qu'avec les exceptions héritant de RuntimeException ! L'avantage d'une RuntimeException est qu'elle n'a pas besoin d'être déclarée dans la signature de la méthode (throws). Par contre avec une Exception standarde (et en mettant à jour les signatures), cela fonctionne parfaitement :

public class ProcessorBean implements ProcessorLocal
{
   public String process() throws RemoteException
   { 
      throw new RemoteException("error"); //RemoteException n'hérite pas de RuntimeException
   } 
}

C'est vraiment un gros cafouillage et je ne comprends vraiment pas pourquoi cela se comporte si différement avec une RuntimeException... Non content de faire méchamment planter le moteur EJB, les messages d'erreurs sont vraiment d'une inutilité totale ! J'espère que ce genre de soucis seront réglés dans la prochaine version de Glassfish. Ceci dit, Glassfish reste tout de même une des implémentations Java EE la plus aboutie à ce jour et tout cela devrait aller en s'améliorant :)

En annexe, le projet sous NetBeans reproduisant l'exemple que j'ai mis ci-dessus. Pour l'utiliser, il suffit d'aller sur l'interface asadmin et d'utiliser le WebService Tester. J'ai également posté un message sur les forums de Sun.

Et voici une solution !