📜 Contexto histórico: Java en los años 90
Para entender Enterprise JavaBeans debemos transportarnos a finales de los años 90. Java acababa de irrumpir en el mundo de la programación con una promesa revolucionaria: "Write Once, Run Anywhere". James Gosling y su equipo en Sun Microsystems habían creado un lenguaje que por primera vez hacía realista la idea de escribir código portable entre plataformas.
Foto: Peter Campbell · CC BY-SA 4.0 · Wikimedia Commons
En aquel momento, el mundo empresarial vivía una transformación: las grandes corporaciones querían migrar sus sistemas de mainframes y cliente-servidor hacia aplicaciones distribuidas basadas en Internet. Los bancos, las aseguradoras y las grandes compañías necesitaban un estándar que les permitiera construir aplicaciones con transacciones distribuidas, seguridad, persistencia y comunicación remota entre componentes.
La respuesta de Sun Microsystems fue ambiciosa: la plataforma J2EE (Java 2 Platform, Enterprise Edition), presentada en 1999. Y el corazón de J2EE era una especificación llamada Enterprise JavaBeans (EJB). La idea era brillante sobre el papel: definir un modelo estándar de componentes del lado del servidor que cualquier fabricante pudiera implementar.
El contexto es importante porque en los años 90 no existían las alternativas que tenemos hoy. No había Spring, no había Hibernate, no había contenedores ligeros. Las opciones eran CORBA (aún más complejo que EJB), código propietario de cada fabricante, o EJB. Para muchas empresas, EJB parecía la opción más razonable.
🏗️ ¿Qué eran los Enterprise JavaBeans?
La especificación Enterprise JavaBeans definía una arquitectura para un sistema de objetos transaccionales distribuidos basado en componentes. En términos más sencillos: EJB era un conjunto de reglas que permitían crear piezas de software (llamadas beans) que vivían dentro de un servidor de aplicaciones y que el servidor gestionaba automáticamente.
La promesa central de EJB era que el desarrollador solo tenía que escribir la lógica de negocio, y el servidor de aplicaciones — a través de su contenedor EJB — se encargaría de todo lo demás: transacciones, seguridad, persistencia, comunicación remota, gestión de memoria y concurrencia.
El modelo de programación EJB
| Elemento | Función | Descripción |
|---|---|---|
| Interface Remote | Métodos de negocio | Declaraba los métodos que el cliente podía invocar. Extendía javax.ejb.EJBObject |
| Interface Home | Ciclo de vida | Permitía crear, buscar y destruir beans. Extendía javax.ejb.EJBHome |
| Clase Bean | Implementación | Contenía la lógica de negocio. Implementaba EntityBean o SessionBean |
| Descriptor XML | Configuración | Archivo ejb-jar.xml con transacciones, seguridad y relaciones |
Es decir, para escribir un simple componente de negocio, había que crear al menos 3 archivos Java y 1 archivo XML. Esto contrastaba enormemente con lo que un desarrollador necesita hoy en Spring Boot: una sola clase anotada.
⚙️ Arquitectura EJB: contenedores, beans y contratos
Los Enterprise JavaBeans eran componentes que se ejecutaban en un entorno especial llamado contenedor EJB. El contenedor hospedaba y gestionaba los beans de manera similar a como un servidor web hospeda servlets. Un bean enterprise no podía funcionar fuera de su contenedor.
Servicios del contenedor EJB
| Servicio | Descripción |
|---|---|
| Acceso remoto | Clientes en otras máquinas podían invocar métodos del bean a través de la red |
| Transacciones | Gestión automática de transacciones (commit/rollback) sin código explícito |
| Seguridad | Control de quién podía acceder a qué métodos del bean |
| Persistencia | Almacenamiento y recuperación automática de datos (en Entity Beans) |
| Concurrencia | Gestión del acceso simultáneo de múltiples clientes |
| Ciclo de vida | Creación, destrucción y reutilización de beans según la demanda |
El contenedor aislaba al bean del acceso directo de las aplicaciones cliente. Cuando un cliente llamaba a un método del bean, el contenedor interceptaba la llamada para aplicar seguridad, transacciones y persistencia de forma transparente.
El mecanismo de stubs y skeletons
Para la comunicación entre máquinas, EJB utilizaba un mecanismo heredado de Java RMI basado en stubs (representantes locales) y skeletons (receptores remotos):
CLIENTE (Máquina A) RED SERVIDOR (Máquina B)
┌──────────────┐ ┌──────────────┐
│ Aplicación │ │ Contenedor │
│ cliente │ │ EJB │
│ ┌────────┐ │ Mensaje de red (JRMP/IIOP) │ ┌────────┐ │
│ │ STUB │──┼──────────────────────────────────►│ │SKELETON│ │
│ │(proxy) │◄─┼──────────────────────────────────◄│ │ │ │
│ └────────┘ │ Resultado serializado │ └───┬────┘ │
│ Parece un │ │ ▼ │
│ objeto │ │ ┌────────┐ │
│ local │ │ │ BEAN │ │
└──────────────┘ │ │ (real) │ │
└──────────────┘
El stub implementaba el interface remoto pero no contenía lógica de negocio: era un proxy de red. Cuando el cliente invocaba un método, el stub enviaba un mensaje al skeleton, que delegaba al bean real. Desde la perspectiva del cliente, parecía un objeto local.
🧩 Tipos de beans: entidad, sesión y mensaje
Bean de Entidad (Entity Bean)
Representaban datos persistentes en una base de datos. Cada instancia correspondía a un registro. Por ejemplo, un ClienteBean representaba un cliente almacenado en la tabla de clientes.
// Interface Remote — métodos de negocio
public interface Cliente extends javax.ejb.EJBObject {
String getNombre() throws RemoteException;
void setNombre(String nombre) throws RemoteException;
String getDireccion() throws RemoteException;
}
// Interface Home — ciclo de vida
public interface ClienteHome extends javax.ejb.EJBHome {
Cliente create(String nombre) throws CreateException, RemoteException;
Cliente findByPrimaryKey(Integer id) throws FinderException, RemoteException;
}
// Clase Bean — implementación real
public class ClienteBean implements javax.ejb.EntityBean {
private EntityContext ctx;
public String nombre;
public String direccion;
public String getNombre() { return nombre; }
public void setNombre(String n) { this.nombre = n; }
// 7 métodos de ciclo de vida OBLIGATORIOS (la mayoría vacíos)
public void setEntityContext(EntityContext ctx) { this.ctx = ctx; }
public void unsetEntityContext() { ctx = null; }
public void ejbActivate() { }
public void ejbPassivate() { }
public void ejbLoad() { }
public void ejbStore() { }
public void ejbRemove() { }
}
Bean de Sesión (Session Bean)
Representaban procesos o tareas de negocio: procesar un pedido, calcular impuestos, reservar una habitación. Existían dos subtipos:
| Subtipo | Estado | Ejemplo |
|---|---|---|
| Stateful | Mantiene estado entre llamadas del mismo cliente | Carrito de compras |
| Stateless | Sin estado entre llamadas | Calculadora de impuestos |
Bean de Mensaje (Message-Driven Bean)
Introducidos en EJB 2.0, los Message-Driven Beans (MDB) procesaban mensajes asíncronos desde colas JMS. No tenían interfaces Remote ni Home. Este fue el tipo de bean mejor diseñado y su concepto sigue vigente hoy en Apache Kafka y RabbitMQ.
🔌 Los interfaces Home y Remote
Cada bean necesitaba dos interfaces obligatorios: el Home (extendiendo javax.ejb.EJBHome) para el ciclo de vida (crear, buscar, destruir) y el Remote (extendiendo javax.ejb.EJBObject) para los métodos de negocio.
// 1. Obtener el contexto JNDI
Context ctx = new InitialContext(propiedades);
// 2. Buscar el interface Home en JNDI
Object ref = ctx.lookup("java:comp/env/ejb/ClienteHome");
ClienteHome home = (ClienteHome) PortableRemoteObject.narrow(
ref, ClienteHome.class);
// 3. Crear un bean a través del Home
Cliente cliente = home.create("María García");
// 4. Usar los métodos de negocio
cliente.setDireccion("Calle Mayor 15, Madrid");
// 5. Buscar un bean existente
Cliente otro = home.findByPrimaryKey(new Integer(42));
Observa la cantidad de pasos ceremoniales solo para obtener una referencia a un simple objeto de negocio. El lookup JNDI, el narrow(), el Home como intermediario... Este código repetitivo fue una de las razones principales del descontento de la comunidad.
💥 Los problemas de EJB: por qué la comunidad se rebeló
A principios de los años 2000, la frustración con EJB alcanzó niveles críticos. Los problemas eran múltiples y profundos:
1. Complejidad desproporcionada
Un simple bean requería 3 archivos Java + XML. Para una aplicación con 50 conceptos de negocio: 150+ archivos Java y miles de líneas de XML. La mayor parte era puro boilerplate sin valor de negocio.
2. Entity Beans: un desastre de rendimiento
Los Entity Beans con Container-Managed Persistence (CMP) generaban consultas SQL extremadamente ineficientes. El resultado era aplicaciones lentas que no escalaban.
3. Testeo imposible
Como los beans dependían completamente del contenedor, era prácticamente imposible ejecutar tests unitarios. Para probar un método, había que desplegar en un servidor de aplicaciones completo.
4. Vendor lock-in
Aunque EJB prometía portabilidad, cada servidor (WebLogic, WebSphere, JBoss) tenía descriptores de despliegue adicionales y comportamientos específicos. La portabilidad era más ideal que realidad.
5. Servidores pesados y caros
Ejecutar EJB requería servidores como IBM WebSphere (licencias de decenas de miles de euros) que consumían enormes cantidades de memoria y tardaban minutos en arrancar.
🌱 La revolución Spring: Rod Johnson y el libro que cambió Java
La historia de la caída de EJB y el ascenso de Spring es una de las más fascinantes de la ingeniería de software. Todo comenzó con un libro.
En octubre de 2002, un consultor australiano llamado Rod Johnson publicó "Expert One-on-One J2EE Design and Development" (Wrox Press). Johnson tenía un perfil inusual: doctor en Musicología por la Universidad de Sídney, reconvertido en arquitecto de software empresarial con años de experiencia en la banca de Londres. Su libro contenía una crítica demoledora de EJB, pero no se limitaba a señalar problemas: incluía más de 30.000 líneas de código que demostraban una alternativa funcional.
La tesis central de Johnson era revolucionaria: la mayor parte de la complejidad de EJB era innecesaria. Se podían construir aplicaciones empresariales robustas usando POJOs simples con un contenedor ligero que proporcionara Inyección de Dependencias y Programación Orientada a Aspectos (AOP).
El código de ejemplo de ese libro fue liberado como software libre y, en febrero de 2003, un pequeño equipo creó un proyecto en SourceForge. Lo llamaron Spring Framework, y su versión 1.0 se publicó en marzo de 2004.
Los principios de Spring que derrotaron a EJB
| Principio Spring | Problema EJB que resolvía |
|---|---|
| POJOs simples | Eliminaba las interfaces obligatorias (Home, Remote, EntityBean) |
| Inyección de Dependencias | Sustituía los lookups JNDI manuales por configuración declarativa |
| No requiere app server | Funcionaba con un simple Tomcat (gratuito) en lugar de WebSphere |
| Testeable | POJOs testeables unitariamente sin contenedor |
| AOP | Transacciones y seguridad sin acoplar al código de negocio |
| Integración Hibernate | Reemplazaba Entity Beans con un ORM ligero y eficiente |
En 2004, Johnson publicó un segundo libro aún más contundente: "J2EE without EJB" (junto a Jürgen Hoeller). El título lo decía todo. La comunidad Java votó masivamente con los pies: los nuevos proyectos adoptaron Spring.
// ¡Una sola clase! Sin interfaces obligatorios, sin XML de despliegue
@Service
public class ClienteService {
@Autowired
private ClienteRepository repository;
public Cliente crear(String nombre) {
return repository.save(new Cliente(nombre));
}
public Cliente buscarPorId(Long id) {
return repository.findById(id)
.orElseThrow(() -> new ClienteNotFoundException(id));
}
@Transactional
public void actualizarDireccion(Long id, String direccion) {
Cliente c = buscarPorId(id);
c.setDireccion(direccion);
}
}
La diferencia es brutal: de 3 archivos Java + XML con decenas de líneas de ceremonia, a una sola clase limpia y testeable.
🔄 EJB 3.0: la simplificación tardía
En 2006 se publicó EJB 3.0, que representó un cambio radical en la filosofía:
| Aspecto | EJB 2.x (clásico) | EJB 3.0 (simplificado) |
|---|---|---|
| Interfaces | Obligatorios: Home + Remote | Opcionales. Basta con anotaciones |
| Configuración | Descriptores XML extensos | Anotaciones Java (@Stateless, @Entity) |
| Persistencia | Entity Beans (CMP/BMP) | JPA con @Entity y EntityManager |
| Clase bean | Implementa interfaces EJB | POJO con anotaciones |
| Lookup | JNDI manual | Inyección con @EJB o @Inject |
@Stateless
public class CajeroDeHotelBean {
@PersistenceContext
private EntityManager em;
public void reservarHabitacion(Long clienteId, Long habitacionId) {
Habitacion hab = em.find(Habitacion.class, habitacionId);
Cliente cli = em.find(Cliente.class, clienteId);
hab.setOcupada(true);
hab.setCliente(cli);
}
}
EJB 3.0 fue una mejora enorme, pero llegó demasiado tarde. Para 2006, Spring ya tenía millones de usuarios y un ecosistema maduro. Además, EJB 3.0 seguía requiriendo un servidor de aplicaciones completo. La batalla ya estaba perdida.
🚀 Spring Boot: el estándar de facto moderno
Si Spring Framework ganó la batalla contra EJB, Spring Boot (lanzado en abril de 2014) ganó la guerra definitiva. Spring Boot eliminó la última barrera: la configuración.
Spring Boot introdujo auto-configuración: el framework detecta las bibliotecas del classpath y se configura automáticamente. Un desarrollador puede crear una aplicación web empresarial funcional en minutos.
// 1. Entidad JPA (reemplaza los Entity Beans)
@Entity
public class Cliente {
@Id @GeneratedValue
private Long id;
private String nombre;
private String direccion;
}
// 2. Repositorio (acceso a datos automático)
public interface ClienteRepository
extends JpaRepository<Cliente, Long> {
List<Cliente> findByCodigoPostal(String cp);
}
// 3. Servicio (reemplaza los Session Beans)
@Service
public class ClienteService {
private final ClienteRepository repo;
public ClienteService(ClienteRepository repo) {
this.repo = repo;
}
@Transactional
public Cliente crear(String nombre) {
return repo.save(new Cliente(nombre));
}
}
// 4. API REST (exposición de servicios)
@RestController
@RequestMapping("/api/clientes")
public class ClienteController {
private final ClienteService service;
@PostMapping
public Cliente crear(@RequestBody String nombre) {
return service.crear(nombre);
}
@GetMapping("/{id}")
public Cliente obtener(@PathVariable Long id) {
return service.buscarPorId(id);
}
}
Estas pocas decenas de líneas limpias hacen todo lo que un sistema EJB 2.x necesitaba cientos de líneas, múltiples interfaces y archivos XML para lograr. Se puede testear, arranca en segundos y se despliega como un simple JAR ejecutable.
🌐 Jakarta EE: el legado renombrado
En septiembre de 2017, Oracle transfirió Java EE a la Eclipse Foundation. Como no pudo transferir la marca "Java", la plataforma se renombró a Jakarta EE. El cambio más visible: todos los paquetes javax.* pasaron a jakarta.*.
| Aspecto | Java EE (Oracle) | Jakarta EE (Eclipse) |
|---|---|---|
| Gobernanza | Oracle + JCP (proceso cerrado) | Eclipse Foundation (abierta) |
| Namespace | javax.* | jakarta.* |
| Última versión | Java EE 8 (2017) | Jakarta EE 11 (2024) |
| Servidores | WildFly, GlassFish, WebSphere | WildFly, GlassFish, Open Liberty |
Jakarta EE sigue incluyendo EJB (como jakarta.ejb), pero su relevancia ha disminuido significativamente. Las tecnologías más activas son JPA, JAX-RS, CDI y JSON-B/JSON-P.
⚖️ Comparativa: EJB clásico vs Spring Boot moderno
| Criterio | EJB 2.x (2001) | Spring Boot (2024+) |
|---|---|---|
| Archivos por bean | 3 Java + 1 XML mínimo | 1 clase Java anotada |
| Tiempo arranque | 1-5 minutos | 2-5 segundos |
| Servidor | App server completo | Tomcat embebido (incluido en JAR) |
| Coste licencia | $10.000 - $100.000+/año | $0 (código abierto) |
| Tests unitarios | Prácticamente imposibles | Nativos con JUnit y Mockito |
| Despliegue | EAR/WAR en app server | java -jar app.jar |
| Comunicación | RMI/IIOP con stubs | REST sobre HTTP (JSON) |
| Persistencia | Entity Beans (eliminados) | Spring Data JPA + Hibernate |
| Cloud-native | No diseñado para la nube | Docker/Kubernetes nativo |
| Microservicios | Concepto inexistente | Soporte nativo (Spring Cloud) |
🏚️ ¿Qué queda hoy de EJB? Tecnología legacy
Aplicaciones legacy en mantenimiento: Miles de aplicaciones bancarias y gubernamentales escritas entre 2000 y 2010 todavía funcionan con EJB sobre WebSphere o WebLogic. Se mantienen porque reescribirlas es costoso, no porque EJB sea la mejor opción.
Tecnologías eliminadas o muertas:
| Tecnología | Estado | Reemplazo |
|---|---|---|
| Entity Beans | ❌ Eliminados en EJB 3.2 | JPA |
| Interfaces Home/Remote | ❌ Innecesarios desde EJB 3.0 | Anotaciones + CDI |
| CORBA / IIOP | ❌ Eliminado de Java SE 11 | REST / gRPC |
| Stubs y Skeletons | ❌ Obsoletos desde Java 5 | Proxies dinámicos |
Namespace javax.ejb | ⚠️ En desuso | jakarta.ejb |
📚 Lecciones aprendidas para el desarrollador actual
🎯 La simplicidad gana. Spring derrotó a EJB no por tener más funcionalidades, sino por ser más simple. El código más fácil de escribir, leer y testear siempre se impone a largo plazo.
🎯 Las especificaciones por comité pueden fracasar. EJB fue diseñado por comités de grandes empresas pensando en los escenarios más complejos. El resultado fue una especificación sobreingenieriada.
🎯 La testeabilidad no es opcional. La imposibilidad de hacer tests unitarios con EJB fue una de sus mayores debilidades. Hoy sabemos que el código que no se puede testear no se puede mantener.
🎯 La comunidad manda. No importa cuántas empresas respalden un estándar: si los desarrolladores encuentran algo mejor, migrarán. EJB tenía el respaldo de Sun, IBM y Oracle. Spring era un proyecto open-source pequeño. Spring ganó porque resolvía problemas reales.
🎯 Ninguna tecnología es para siempre. EJB dominó durante una década. Hoy es legacy. Spring Boot domina ahora, pero algún día también será reemplazado. La capacidad de adaptarse es la habilidad más valiosa de un ingeniero de software.
✏️ Ejercicios prácticos
Ejercicio 1: Identificar la época del código
Observa el siguiente fragmento y responde: ¿a qué época/tecnología pertenece? ¿Cuál sería su equivalente moderno?
Context ctx = new InitialContext();
Object ref = ctx.lookup("java:comp/env/ejb/PedidoHome");
PedidoHome home = (PedidoHome) PortableRemoteObject.narrow(ref, PedidoHome.class);
Pedido pedido = home.create("PED-001", clienteId);
pedido.addLinea(productoId, cantidad);
double total = pedido.calcularTotal();
Ver solución
Código EJB 2.x (2001-2006). Pistas: lookup JNDI, PortableRemoteObject.narrow() (CORBA), interface Home para crear instancias. Equivalente moderno:
@Autowired private PedidoService pedidoService;
Pedido pedido = pedidoService.crear("PED-001", clienteId);
pedidoService.addLinea(pedido.getId(), productoId, cantidad);
double total = pedidoService.calcularTotal(pedido.getId());
Ejercicio 2: Refactorizar de EJB a Spring
Reescribe este Entity Bean EJB 2.x usando JPA + Spring Data:
public interface Producto extends javax.ejb.EJBObject {
String getNombre() throws RemoteException;
double getPrecio() throws RemoteException;
int getStock() throws RemoteException;
void reducirStock(int cantidad) throws RemoteException;
}
public interface ProductoHome extends javax.ejb.EJBHome {
Producto create(String nombre, double precio) throws CreateException, RemoteException;
Producto findByPrimaryKey(Integer id) throws FinderException, RemoteException;
Collection findByPrecioMenorQue(double max) throws FinderException, RemoteException;
}
public class ProductoBean implements javax.ejb.EntityBean {
public String nombre; public double precio; public int stock;
// + 7 métodos de ciclo de vida obligatorios vacíos...
}
Ver solución
@Entity
public class Producto {
@Id @GeneratedValue(strategy = GenerationType.IDENTITY)
private Integer id;
private String nombre;
private double precio;
private int stock;
protected Producto() { }
public Producto(String nombre, double precio) {
this.nombre = nombre;
this.precio = precio;
}
public void reducirStock(int cantidad) {
if (cantidad > stock) throw new IllegalArgumentException("Stock insuficiente");
this.stock -= cantidad;
}
// Getters y setters
}
public interface ProductoRepository extends JpaRepository<Producto, Integer> {
List<Producto> findByPrecioLessThan(double max);
}
De 3 archivos con ~40 líneas de boilerplate a 2 archivos donde cada línea tiene propósito real. Los métodos de búsqueda se generan automáticamente por Spring Data.
Ejercicio 3: Línea temporal de la evolución
Ordena cronológicamente: a) Spring Boot b) EJB 1.0 c) Rod Johnson publica su libro d) Oracle transfiere Java EE e) EJB 3.0 f) Eliminación de CORBA de Java SE 11
Ver solución
b) 1998 — EJB 1.0: Nace la especificación que prometía simplificar el desarrollo empresarial.
c) 2002 — Rod Johnson publica su libro: Demuestra que EJB es innecesariamente complejo. Da origen a Spring (2003).
e) 2006 — EJB 3.0: Simplificación radical, pero llega demasiado tarde.
a) 2014 — Spring Boot: Auto-configuración, servidor embebido, arranque en segundos. Dominio total de Spring.
d) 2017 — Oracle transfiere Java EE: Nace Jakarta EE bajo Eclipse Foundation.
f) 2018 — CORBA eliminado de Java SE 11: Limpieza definitiva de la tecnología fundacional de EJB.
❓ Preguntas frecuentes sobre Enterprise JavaBeans: Historia y Evolución de los Componentes Empresariales en Java
Las dudas más comunes respondidas de forma clara y directa.
💬 Foro de discusión
¿Tienes dudas sobre Enterprise JavaBeans: Historia y Evolución de los Componentes Empresariales en Java? Comparte tu pregunta con la comunidad.
Todavía no hay mensajes. ¡Sé el primero en participar!