August 2007


We recently wrote a software using Java 1.4, which prevented us from using some nice developer testing tools that required at least Java 5 (also known as 1.5). Namely these were JDave that would have underlined that we really wanted to do behavior-driven development (BDD), and WicketBenchTestCase of Wicket Bench, which allows easy testing of individual Apache Wicket components.

When discussing this, our coworker Ville remarked that often it makes sense to put the tests of the project in a separate module (something that would be a submodule in Maven 2 multiproject project, and a separate Eclipse project or another module in IntelliJ IDEA). This way it would be trivial to use a different JDK for test and production code. Nevertheless we decided to keep it simple by doing everything in a single Java 1.4 module; the software was small and we felt that the marginal hassle of going from n to n + 1 modules is highest when n == 1.

We wanted to do system testing on a production-resembling platform as soon in the project as possible, but due to various circumstances out of our control, we only got there rather late in the project. And when we finally had deployed the software in the real application server, we were baffled by unexpected errors in some LDAP searches done by our application.

After a quick screening of differencies between our development environments and the system test environment, we tried putting the (proprietary) production JDK also to our development machines. For our great satisfaction, some of the various developer tests (unit + integration tests covering about 90 % of all program code lines, ahem! :)) started failing with the exactly same errors as in the test environment. So this was a case of the JRE abstraction leaking.

Now that the problem could easily be reproduced on any development machine, fixing it was rather straightforward. We even changed our Continuum server to use the production JVM + JRE, and verified that after the fixes, the software was still working as expected on the Java implementation originally used in development.

And then a while later it occurred to me what a good idea, albeit for wrong resons, it had been to write our developer tests on the same Java version as the target environment. If not, reproducing and fixing our bug might have been a lot more difficult.

Advertisement

This is a step-by-step guide for converting an old Enterprise Java Beans (EJB) application (EJB versions 1 and 2 in mind) to use Spring Framework and Hibernate.

The aim of this article is to tell in a practical and detailed way how this can be done. The question of why it is sometimes a good idea has been widely discussed, but nevertheless I address it briefly in an earlier post.

Please bear in mind that you always need to know what you are doing, and to adapt this procedure to your environment. Also, this kind of work should only be done when it clearly provides value. My experience has been that as with any refactorings or other functionality-preserving changes, this kind of improvement is best done in conjunction with changing or adding functionality, when the code needs to be touched upon anyway.

For the sake of the example, I created a little EJB 2 application and deployed it on JBoss. You can check the application out from or browse it in Subversion. For building and running the application, there are instructions in README.txt.

The main application PersonFinderApp is a command line program for searching persons in the database:

    public static void main(String... args) {
        if (args.length < 1) {
            throw new IllegalArgumentException(
            "Please supply a search term as a command-line parameter");
        }

        String searchTerm = args[0];
        List results = instance.findPersons(new SearchParameters(searchTerm));
        for (Person result : results) {
            try {
                out.println(MessageFormat.format("{0}, {1} {2} ({3})", result,
                    result.getFirstName(), result.getLastName(), result.getUserName()));
            } catch (RemoteException e) {
                throw new RuntimeException(e);
            }
        }
    }

This is done as follows:

  • PersonFinderApp gets a stateless session EJB PersonManager using container-managed transactions (CMT)
  • PersonManager invokes the finder method findBySearchTerm of PersonEntity EJB to get the results
  • PersonFinderApp outputs the results

PersonEntity is a container-managed (CMP) entity EJB. Its home interface provides methods for creating a new entity, finding an entity by its primary key, and finding an entity by a search string with EJB QL. The finder EJB QL is specified in ejb-jar.xml that contains all EJB configuration. The EJB container of JBoss handles the persistence of PersonEntity objects to a Hsqldb database embedded in JBoss.

So this simplistic example provides the two common cases to convert to non-EJB solutions: a stateless session bean that takes care of transaction management and service functionality, and an entity bean that handles database persistence of domain objects.

Prerequisites

Before doing anything, ensure that you have good automatic, in-container regression tests in place verifying that the EJB application works as expected. I have written these as JDave specifications. For example PersonFinderAppSpec.InContainer tests the main application which uses the deployed EJBs on the container:

public PersonFinderApp create() throws RemoteException {
    PersonDao dao = new ServiceLocatorImpl().getPersonDao();
    person = dao.create("appPerson-" + System.currentTimeMillis(), "Bill", "Apperson");
    return new PersonFinderApp();
}

public void destroy() throws RemoveException, RemoteException {
    person.remove();
}

public void findsPersonsFromDatabase() {
    List results = context.findPersons(new SearchParameters("Bill"));
    specify(results, contains(person));
}

There are also more fine-grained specifications.

Changing the stateless session bean to a Spring bean

I’ll start with the easier case of converting PersonManager to a normal Spring bean. First I’m adding a dependency to Spring 2.0.6 in the project pom.xml files and adding to the shiny new applicationContext.xml a Spring transaction manager bean that the new Spring bean will use. For this, I add these specification methods to ServiceLocatorImplSpec:

public void getsSpringApplicationContext() {
    specify(context.getApplicationContext(), does.not().equal(null));
}

public void getsSpringTransactionManagerFromApplicationContext() {
    ApplicationContext applicationContext = context.getApplicationContext();
    specify(applicationContext.getBean("txManager"), does.not().equal(null));
}

For the sake of completeness, I’m first making it use the JTA Transaction manager of JBoss. In the case of this little application, and also in many real-world-cases, transactions are not so complex that they would have both Spring beans and session EJBs operating within the same transaction. But if this happens, it’s good to know that Spring supports using JTA from the application server for its own declarative transaction management. In our example application that uses a remote client, there is a glitch that on client-side, you can only use UserTransaction because the actual JTA transaction manager is only available within the server. For server-side JTA transactions you would put

       <property name="transactionManagerName" value="java:/TransactionManager"/>

for the Spring txManager bean (JtaTransactionManager). The user transaction which we will use is configured like this:

  <bean id="txManager" class="org.springframework.transaction.jta.JtaTransactionManager"
dependency-check="none">
    <property name="autodetectTransactionManager" value="false"/>
    <property name="userTransactionName" value="UserTransaction"/>

I also make a new ApplicationContextHolder class with acts as the glue code singleton for our legacy app. It takes care of creating the Spring application context from the XML configuration:

public class ApplicationContextHolder {
    private static ApplicationContext context;

    public static ApplicationContext getContext() {
        if (context == null) {
            initContext();
        }
        return context;
    }

    private static void initContext() {
        context = new ClassPathXmlApplicationContext("classpath:applicationContext.xml");
    }
}

ServiceLocatorImpl.getApplicationContext() just delegates to this class. The implementation is not thread safe, so it assumes that Spring context usage does not start on many threads at the same time.

To make things easier, PersonManager uses some business interfaces to abstract the EJB access. The business interfaces are PersonFinder and PersonDao. This is good practice and often refactorable to existing EJB applications, if not present. The only problem with old remote EJBs is that the method signatures get littered with throws RemoteException.

And now to changing the actual bean: I first create another specification method in PersonManagerSpec:

public void isFoundInSpringApplicationContext() {
    ApplicationContext applicationContext = new ServiceLocatorImpl().getApplicationContext();
    specify(applicationContext.getBean(PersonManager.ID, PersonManager.class), does.not().equal(null));
}

and it fails as expected:

org.springframework.beans.factory.NoSuchBeanDefinitionException: No bean named 'personManager' is defined

Now I remove EJBObject from the PersonManager extends list and make PersonManagerBean implement it directly, instead of SessionBean and the business interface PersonFinder (because PersonManager already extends PersonFinder). I also remove throws CreateException from the create method signature and handle it inside the method, because it now clashes with the signature of PersonDao.create() — but actually all EJB-specific throws clauses can now be removed.

PersonManagerBean still needs the @Transactional annotation to get declarative transaction management by Spring, and the annotation in turn requires

  <tx:annotation-driven transaction-manager="txManager"/>

in applicationContext.xml.

Now it’s time to add the actual Spring bean configuration for PersonManager:

  <bean id="personManager" class="org.laughingpanda.ejb_migration_example.ejb.PersonManagerBean"/>

IDEA now complains that I should disable dependency checking or set the property sessionContext. So I delete the redundant setter method, and all the remaining EJB API methods as well, from PersonManagerBean.

And now the test passes in the IDE! However, I still need to change ServiceLocatorImpl to use the new Spring bean, and the stateless session bean needs to be removed from ejb-jar.xml to get the EJB build pass, and to remove the previously deployed EJB.

So I delete the whole bean definition from ejb-jar.xml and change the JNDI lookup

private PersonManager getPersonManager() {
    Object objref = lookup("PersonManagerEJB");
    PersonManagerHome home = (PersonManagerHome) PortableRemoteObject.narrow(objref, PersonManagerHome.class);
    try {
        return home.create();
    } catch (Exception e) {
        throw new RuntimeException(e);
    }
}

to Spring application context lookup

private PersonManager getPersonManager() {
    return (PersonManager) getApplicationContext().getBean(PersonManager.ID);

and rebuild and deploy, and now everything works!

I now remove the unnecessary throws RemoteException clauses from the business interfaces, and their handling from PersonFinderApp.findPersons method, and after testing the application with IDE and Maven and from command-line, our work on PersonManager is done for now.

The procedure might seem involved, but is in fact almost mechanical. Because it must be done manually, it’s important to have automatic regression tests for the actual functionality, as well as to check the relevant functionality manually. For example classloader issues may arise because code might get moved from the EJB classloader to another one lower on the classloader hierarchy.

To recap, to change a session bean to a Spring-managed Java bean

  1. set up Spring and add the necessary service lookups or configurations there (transaction manager, datasource…)
  2. make Spring context available to your service locator via a singleton class holding the application context
  3. ensure that you have a regression test for the functionality that uses the relevant EJB, and run it to verify that it passes
  4. create a new, failing integration test for getting the (soon-ex-enterprise) bean from Spring context
  5. remove EJB-specific things from the local or remote EJB interface
  6. remove EJB-specific things from the bean class, and make it directly implement its interface
  7. add the bean, that is now a plain old java object (POJO), to Spring
  8. add Spring transaction management to the bean
  9. change the service locator to lookup the Spring bean instead of the EJB
  10. remove the old session bean from ejb-jar.xml
  11. do a clean build + deploy, run the regression test and correct errors if any
  12. verify that the real application still works as expected
  13. check that anything looks clean and remove any extra warts left behind, such as the old EJB home interface

When removing more EJBs after the first one, you skip the two first steps, as the Spring plumbing is already in place. But when the beans start to get dependencies on each other, you should change them to let Spring inject them instead of the Spring beans looking up other Spring beans via the service locator. Below there will be an example of this.

The current state of the software is in the 0.2 tag in Subversion.

Personmanager is now a Spring-managed POJO, even though it incidentally still uses the PersonEntity EJB. So that’s what we’ll turn to next.

Changing the entity bean to a POJO + Hibernate DAO

I start by putting Hibernate dependency to the pom.xml and updating it to the IDE configuration files. Then I make a failing integration spec for the soon-to-be Spring bean PersonDao

@RunWith(JDaveRunner.class)
public class PersonDaoSpec extends Specification {
    public class InSpring {
        public PersonDao create() {
            return null;
        }

        public void isFoundInApplicationContext() {
            ApplicationContext applicationContext = ApplicationContextHolder.getContext();
            PersonDao fromSpring = (PersonDao)
                    applicationContext.getBean(PersonDao.ID, PersonDao.class);
            specify(fromSpring, does.not().equal(null));
        }
    }

I then start coding a Hibernate implementation of PersonDao, namely PersonHibernateDao. To get some sense to the method public PersonEntity create(String userName, String firstName, String lastName) I make a simple java bean out of PersonEntityBean, making it implement PersonEntity directly. If you are using data transfer objects (DTO), as is common in EJB 1 and EJB 2 applications, they can be used as a basis for the domain object implementations.

I also remove extends EJBObject from PersonEntity. While implementing the create method, I notice that all methods in Person throw RemoteException, which can now be removed.

For now, I end up with a Hibernate DAO class like this:

    public PersonEntity create(String userName, String firstName, String lastName) {
        PersonEntity person = new PersonEntityBean();
        person.setUserName(userName);
        person.setFirstName(firstName);
        person.setLastName(lastName);

        Session session = sessionFactory.getCurrentSession();
        Long id = (Long) session.save(person);
        return (PersonEntity) session.get(PersonEntityBean.class,id);
    }

I realise that I have to change PersonEntitySpec to use the DAO, and add the remove method in PersonDao.

Then I add the new DAO and Hibernate configuration to the Spring configuration file:

  <jee:jndi-lookup id="dataSource" jndi-name="DefaultDS"/>
  <bean id="sessionFactory" class="org.springframework.orm.hibernate3.annotation.AnnotationSessionFactoryBean"
        dependency-check="none">
    <property name="configurationClass" value="org.hibernate.cfg.AnnotationConfiguration"/>
    <property name="annotatedClasses">
      <list>
        <value>org.laughingpanda.ejb_migration_example.PersonBean</value>
      </list>
    <property name="schemaUpdate" value="true"/>
    <property name="hibernateProperties">
        <prop key="hibernate.dialect">org.hibernate.dialect.HSQLDialect</prop>
        <prop key="hibernate.show_sql">false</prop>
        <prop key="hibernate.cglib.use_reflection_optimizer">false</prop>
        <prop key="hibernate.query.substitutions">true=1, false=0</prop>
        <prop key="hibernate.cache.provider_class">org.hibernate.cache.NoCacheProvider</prop>
    </property>

  <bean id="personDao" class="org.laughingpanda.ejb_migration_example.PersonHibernateDao"
          autowire="constructor"/>

and now the bean is found in Spring context.

Now, because the EJB classes are now longer EJB classes on the client, PersonEntitySpec starts failing. I need to change PersonManagerBean to use the new DAO instead of PersonEntityHome. So I do that, and add the rest of PersonEntityHome methods to PersonDao, and change ServiceLocatorImpl.getPersonDao() to return the personDao Spring bean.

When running the specs again, Hibernate tells us that there is no mapping for PersonEntityBean:

org.hibernate.MappingException: Unknown entity: org.laughingpanda.ejb_migration_example.ejb.PersonEntityBean

So I tag the relevant portions of PersonEntityBean with the JPA annotations and creating new lines to the database now works.

The finder method still needs to be implemented with Hibernate, for which I use a simple Hibernate Query Language (HQL) query

    public Collection findBySearchTerm(String searchTerm) throws FinderException, RemoteException {
        return getSession().createQuery("from PersonEntityBean person " +
                "where userName like ? or firstName like ? or lastName like ?")
                .setString(0, searchTerm).setString(1, searchTerm).setString(2, searchTerm)
                .list();
    }

and also make simple equals, hashCode and toString implementations. And now all tests pass again in the IDE!

Deploying the application still fails, because the entity bean needs to be removed from ejb-jar.xml. Empty enterprise-beans element would not be valid, so I end up deleting the whole file. This in turn makes the EJB build fail, so I remove the whole person-ejb module from the pom.xml files. Now the tests pass both from IDE and from Maven, and the project can be built and deployed. And it still works from the command-line too.

Now it’s time to clean up. First of all, there are a lot of unused throws clauses that I remove, and PersonEntityHome can be thrown away as well. While we’re at it, I also remove PersonMangerHome that I forgot when converting PersonManagerBean. PersonEntity interface is now redundant

public interface PersonEntity extends Person {
}

so I inline that as well. I don’t know if the Person interface makes any sense either, but leave it for now. I rename PersonEntityBean to PersonBean, though, and remove the ejbCreate and other EJB methods from it. It can now be used in PersonFinderAppSpec instead of the inner class PersonStub used until now.

PersonManagerBean still looks up personDao from ServiceLocator, so I replace that with autowiring by name now that personDao is a Spring-managed bean as well. This causes PersonManagerSpec to fail, so I make it get the bean from application context instead of constructing it by itself. But then again, PersonManagerBean doesn’t really do anything interesting other than delegate to personDao, except this method

public List findPersons(SearchParameters searchParameters) {
    try {
        String termWithWildcard = searchParameters.getSearchTerm() + "%";
        Collection rawResults = personDao.findBySearchTerm(termWithWildcard);
        List results = new ArrayList(rawResults.size());
        for (Object o : rawResults) {
            results.add((Person) o);
        }
        return results;
    } catch (Exception e) {
        throw new RuntimeException(e);
    }
}

so I just move the method to PersonHibernateDao, make PersonDao extend PersonFinder, and remove the whole PersonManager. This enties moving relevant PersonManagerSpec contents to PersonDaoSpec as well.

This can be done, because

  • Unlike the container-managed entity bean PersonEntity EJB, the new PersonHibernateDao is a POJO to which I can add arbitrary methods taking arbitrary kind of parameters — in this case, a finder method taking in a custom java class SearchParameters.
  • With the declarative transaction management of Spring, we can make the DAO itself transactional and customise its transaction attributes as needed (on Java 5, simply by adding the @Transactional annotation to it). Indeed my co-worker Joni has shown me that in many cases, you can just make the DAOs transactional, and use them directly from the client code. Separate services are only really needed when you need more complex transactions spanning across different transactional services, and even then it is often viable to use ad hoc transactions, for example with the handy TransactionTemplate from Spring.

After some cleanup, the whole PersonHibernateDao is quite lean.

So, to change an entity EJB to a Hibernate-persisted POJO:

  1. set up Hibernate
  2. ensure that you have a regression test for the functionality that uses the relevant EJB, and run it to verify that it passes
  3. create a new, failing integration test for getting the soon-to-be Hibernate DAO from Spring context
  4. remove EJB-specific things from the local or remote EJB interface
  5. create a POJO implementation of your domain objects, using the local or remote EJB interface or the DTO class as the base, and make sure to have good equals and hashCode implementations for it
  6. create the new Hibernate DAO, moving the relevant Home interface methods to it
  7. add the DAO to Spring
  8. change the service locator to lookup this new Spring bean instead of the EJB
  9. verify that tests using the DAO now fail because the domain object class is not mapped yet
  10. add Hibernate mapping for the domain object class, either by JPA annotations or manual .hbm.xml mapping files
  11. run the tests using the DAO and correct errors if any
  12. remove the entity EJB from the ejb-jar.xml
  13. do a clean build + deploy, run the regression test and correct errors if any
  14. verify that the real application still works as expected

Now that we don’t have any EJBs left, I also remove the whole person-ejb module. The current state of the project is in the 0.3 tag in Subversion.

Removing the rest of the container dependencies for easier development

The application still uses DataSource and transaction manager from the application server. It doesn’t use the EJB container any more, so it could already be run on an express version of a commercial application server, but requires the application server to be running to even run the integration tests.

So I take Apache Commons DBCP connction pool in use, and replace looking up the JBoss DataSource from JNDI with the commons-dbcp BasicDataSource. I also change the Spring transaction manager from the JTA transaction manager to HibernateTransactionManager.

  <bean id="txManager" class="org.springframework.orm.hibernate3.HibernateTransactionManager"/>
  <tx:annotation-driven transaction-manager="txManager"/>

  <bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource">
    <property name="driverClassName" value="org.hsqldb.jdbcDriver"/>
    <property name="url" value="jdbc:hsqldb:hsql://localhost:1701"/>
    <property name="username" value="sa"/>
    <property name="password" value=""/>
    <property name="defaultAutoCommit" value="false"/>

In ServiceLocatorImpl, there was still a JNDI lookup for the DataSource, so I replace it with a lookup to the Spring ApplicationContext. I run the tests from IDE, rebuild the application and run the tests with maven, run the application, and everything still works.

But the real acid test of running the tests with JBoss shut down still fails, because Hsqldb is being run in server mode within JBoss. Fortunately separating settings between test and production mode is easy enough in Spring:

  <bean id="dataSourceProperties" class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
    <property name="location" value="datasource.properties" />

  <bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource">
    <property name="driverClassName" value="org.hsqldb.jdbcDriver"/>
    <property name="url" value="${db.url}"/>
    <property name="username" value="sa"/>
    <property name="password" value=""/>
    <property name="defaultAutoCommit" value="false"/>

I put the server mode configuration in the production version of datasource.properties, and the in-memory-version in the test source path, and now the tests run in the IDE! In Maven 2.0.7, the test classpath override doesn’t yet work, so for now Maven still requires the separate database instance. Anyway, all code may now be easily developed just within the IDE.

I also remove all JBoss and EJB references from the project now that they are not needed any more, and have a general look on what the application is like at the moment. At least PersonBean doesn’t need to be in the ejb subpackage any more, and the whole package can be removed as it is now empty. Then I tag the version 0.4 where I stop.

All questions, corrections and comments are most welcome. If you apply these instructions, be sure to have good regression tests, to move in little steps, and to know what you’re doing on each step. Happy EJB-hunting!

This article is dedicated to my workmate Markus Hjort.

I am about to publish a detailed hands-on guide for migrating a legacy Enterprise Javabeans (EJB) version 1 or 2 application to a Spring Framework + Hibernate stack. While the guide will concentrate on providing a concrete example of such a migration, I will here give a brief discussion on why and when such a migration would be justified.

For a more authoritative source, I recommend Rod Johnson’s excellent books such as J2EE Design and Development and J2EE Development without EJB. The Spring Framework grew out of Rod’s work outlined in J2EE Design and Development.

Now (in year 2007) everybody agrees that EJB specification versions 1 (from year 1998) and 2 (2001) have inherent problems. It is delightful that EJB 3 (2006) has addressed many shortcomings of the earlier versions, often in ways suggested by leading open source frameworks, though proprietary open source solutions keep on evolving more rapidly and maybe a bit more pragmatically than the standard.

A brief genealogy of the earlier EJB versions can be found in a good post by Floyd Marinescu. The article underlines perhaps the biggest problem of the legacy EJB versions: the EJB 1 specification aims to make every EJB component reusable, even remotely over a network connection. While this provides little value in most enterprise cases, it adds complexity and API clutter. To achieve the questionable goal, EJB components have their lifecycle completely managed by the EJB container, which makes their out-of-container use nearly impossible.

I remember how using EJBs in place of direct RMI seemd to make perfect sense when I was reading Enterprise JavaBeans (third edition) by Richard Monson-Haefel. I had just been working on an RMI project for some months, and like Monson-Haefel, thought I could see how EJBs might have made our project easier. Monson-Haefel’s definition of EJB was

Enterprise JavaBeans is a standard server-side component model for component transaction monitors. (p. 5)

But then I became slightly confused when seeing that in practice, session EJBs were often used on systems that either were or should have been collocated. And entity EJBs seemed stranger still, as the object-relational mapping of container-managed persistence (CMP) was very cumbersome, and bean-managed persistence did not seem to offer any added value over the traditional way of persisting POJOs with JDBC.

The old EJB approach also gave false promises of being able to perfectly automate and abstract some fundamental programming issues, much in line with the RAD tools promises of making programming trivial:

EJB is designed to make it easy for developers to create applications, freeing them from low-level system details of managing transactions, threads, load balancing, and so on. Application developers can concentrate on business logic and leave the details of managing the data processing to the framework.

“A beginner’s guide to Enterprise JavaBeans”, By Mark Johnson, JavaWorld.com, 10/01/98

More problems arise from the classloader scheme of EJB. While good in some cases, in a typical collocated, single-application case, the separation of EJB classes and their transitive dependencies to another classloader invisible to e.g. the web application classloader just makes development-time deployments slower than they would be without EJBs. This reduces developer productivity drastically.

Lately Test-Driven Development has made good progress in making its way to a recommended practice in software development. Old EJB standards have the problem that especially entity beans are impossible to construct outside the container, making unit testing hardly possible. Also using service lookups (from JNDI) instead of dependency injection makes unit testing EJB code complicated; consider

PersonDao mockDao = mock(PersonDao.class);
PersonFinderBean finder = new PersonFinderBean(mockDao);

versus

MockContextFactory.setAsInitial();
Context ctx = new InitialContext();
MockContainer mc = new MockContainer(ctx);
SessionBeanDescriptor dd = new SessionBeanDescriptor("java:comp/env/ejb/PersonDaoEjb",
    PersonDao.class, PersonDao.class, mock(PersonDao.class))) ;
mc.deploy(dd);
PersonFinderBean finder = new PersonFinderBean();

(I adapted the latter example from “Streamlining Your EJB Tests With MockEJB” from BEA dev2dev, by Eoin Woods and Alexander Ananiev 10/17/2005.)

These two kinds of inversion of control, service locators versus dependency injection, are discussed in the classic article by Martin Fowler and EJB 3 has adopted dependency injection in favor of JNDI lookups from client code.

Whereas the new EJB 3 specification fortunately addresses many of the problems of the easier versions, my hunch is that given no political constraints, Spring + Hibernate is still bound to be more productive, because of the greater flexibility and speed of development than EJB. For attaining a more objective view, there are a lot of more or less slanted EJB 3 and Spring comparisons around, such as

Two things are certain:

  1. EJB 1 and 2 have widely acknowledged problems. Whether or not these problems warrant reacting to them depends on the case. A working solution must not be tampered with just for the sake of it. Often it makes sense to improve it in conjunction with other changes, such as delivering new, useful functionality or fixing bugs.
  2. EJB 3 and Spring / Hibernate both provide an approach radically different from the EJB 1 or 2 world. If you suffered with EJB 2, you shouldn’t discard EJB 3 because of that. But it’s always worthwhile to check out proprietary, more independent open source solutions in addition to the standard.