In this blog post I will try to explain how to replace Jakrata EE EJBs with CDI beans. In onw of the future releases of Jakarta EE (possible version 12) the EJB concepts will be fully replaced by CDI technology. The reason simply is that EJBs become more and more outdated as the technology is based on older concepts that today are no longer recommended. Another goal for the replacement is to make developers life easier and not providing two very similar technologies in parallel. The Imixs-Workflow project is fully based on Jakarta EE and we are using also EJBs in some of its core components. So this will also be a kind of travel guide of my own journey from EJB to CDI.
The Basics
So first question: Why will EJBs be removed? The first and most obvious answer is: it does not make sens for the Jakarta EE project to support tow similar technologies in parallel. CDI is the newer technology and already today provides a lot of concepts from EJBs. So often in a Jakrata EE project you can either choose to implement a Service in a EJB or CDI bean without any difference in its result.
One of the more hidden reasons is that EJBs were invented at a time when the Java VM did not yet offer the performance and functionality that it does today. At that time, it was simply not efficiently possible to use a bean instance in a multi-threaded situation without running into a problem with the VMs garbage collector that it could no longer keep up cleaning old objects. The was the reason for the EJB Container and its pooling mechanism. That means in EJB a client always gets an EJB instance exclusive and can use it in a thread save way. If all EJBs from the pool are in use a new client request have to wait until one of the pools EJB instances is free again. This was and is a very robust and thread save mechanism and makes the developers life very easy. In a CDI Container we don’t have this kind of pooling and so the first result is the different code layout of CID implementations.
An EJB implementation typical looks like this:
package com.example;
import jakarta.ejb.EJB;
import jakarta.ejb.Stateless;
import jakarta.ejb.TransactionAttribute;
import jakarta.ejb.TransactionAttributeType;
import jakarta.persistence.EntityManager;
import jakarta.persistence.PersistenceContext;
@Stateless
public class StatelessBeanInEJB {
@PersistenceContext
private EntityManager entityManager;
// The @TransactionAttribute(TransactionAttributeType.REQUIRED) // annotation is optional; this is the default already.
public void transactionalMethod() {
// ...
}
@TransactionAttribute(TransactionAttributeType.REQUIRES_NEW)
public void independentTransactionalMethod() {
// ...
}
}
Now this is how the same looks in CDI with help of the in Jakarta Transactions 2.0:
package com.example;
import jakarta.enterprise.context.ApplicationScoped;
import jakarta.persistence.EntityManager;
import jakarta.persistence.PersistenceContext;
import jakarta.transaction.Transactional;
import jakarta.transaction.Transactional.TxType;
@ApplicationScoped
public class StatelessBeanInCDI {
@PersistenceContext
private EntityManager entityManager;
@Transactional // The annotation value TxType.REQUIRED is optional; this is the default already.
public void transactionalMethod() {
// ...
}
@Transactional(TxType.REQUIRES_NEW)
public void independentTransactionalMethod() {
// ...
}
}
The CDI bean has been marked @ApplicationScoped
and is no longer pooled. And the CDI instances are unsynchronized while EJB instances are synchronized.
Synchronized vs Unsynchronized – Was does this mean?
I’ll explain the important difference between synchronized EJB instances and non-synchronized CDI instances:
EJB (@Stateless) – synchronized:
- With EJBs, each bean instance from the pool is only used by one thread at a time
- The container automatically ensures this thread safety
- If several threads want to access the bean at the same time, they have to fetch a free instance from the pool or wait
This makes implementation easier because you don’t have to worry about thread safety.
However, it can lead to performance degradation under high load because threads have to wait.
CDI (@ApplicationScoped) – unsynchronized:
- A CDI Bean instance can be used by multiple threads in parallel
- There is no automatic synchronization by the container
- The developer is responsible for thread safety
This allows for better performance under high load, as no threads have to wait.
However this requires a more careful implementation to avoid race conditions.
Here is an example:
@ApplicationScoped
public class UnsynchronizedCounter {
private int count = 0; // shared state
// NOT thread-save!
public void increment() {
count++; // can lead into a Race Condition
}
// Thread-save Version
public synchronized void incrementThreadSafe() {
count++;
}
}
So with CDI, we have to pay attention to thread safety ourselves if the bean has shared state. Possible solutions are:
- Using Synchronized Methods/Blocks
- Use thread-safe data structures (e.g. AtomicInteger)
- Working stateless
- Use a narrower scope like
@RequestScoped
The EJB version would automatically be thread-safe, but less performant under high load.
Using instance variables in stateless EJBs was always a very bad practice but is was possible. So if you have clean implementations of EJBs without using instance variables, on the first glance it should be easy to transfere your EJB into a CID bean by just replacing the annotation @Stateless
with @ApplicationScoped
.
But now let’s take a deeper look into the details….
… will be continued ….