Java EE / Jakarta EE
Arquitectura de Jakarta EE: contenedores, perfiles y servidores de aplicaciones
Principiante14 min de lecturaActualizado
Esta página se tradujo del inglés con ayuda de IA y se cotejó con el original. Leer la versión en inglés
Jakarta EE es un conjunto de especificaciones que un servidor de aplicaciones implementa para que tu código se apoye en servicios gestionados en lugar de montarlos por su cuenta. Al terminar esta guía sabrás explicar el modelo de contenedores, nombrar las especificaciones que forman Jakarta EE 10 y sus perfiles, entender qué cambió con el paso de javax.* a jakarta.*, empaquetar correctamente un WAR o un EAR y elegir entre GlassFish, WildFly, Open Liberty, Payara o un simple contenedor de servlets.
Lo que aprenderás
- Describir el modelo multicapa y el papel del contenedor web y del contenedor de enterprise beans
- Enumerar las principales especificaciones de Jakarta EE 10 y qué incluyen los perfiles Platform, Web y Core
- Explicar la historia de J2EE a Jakarta EE y la migración de javax.* a jakarta.*
- Empaquetar una aplicación como WAR o EAR y entender los nombres JNDI portables
- Elegir un servidor de aplicaciones o un runtime para un proyecto nuevo o existente
Cualquier aplicación de servidor que no sea trivial necesita el mismo conjunto de servicios: una entrada HTTP, una forma de hablar con una base de datos dentro de una transacción, un modelo de seguridad, un sitio donde ejecutar trabajo en segundo plano y algún tipo de mensajería. Jakarta EE es la respuesta estandarizada a esa lista en la plataforma Java. Escribes componentes contra un conjunto de API publicadas; un servidor de aplicaciones implementa esas API y aporta los servicios de ejecución, y a cambio tu código apenas contiene fontanería de infraestructura. Entender este reparto de responsabilidades —qué hace el contenedor por ti y qué espera de ti— es la base de todo lo demás en este tema, y además explica buena parte del diseño de Spring y MicroProfile.
De J2EE a Jakarta EE
La plataforma ha cambiado de nombre dos veces y los nombres todavía generan confusión, así que conviene fijar la cronología.
| Versión | Año | Contenido destacado |
|---|---|---|
| J2EE 1.2 – 1.4 | 1999 – 2003 | Servlets, JSP, EJB 1.x/2.x con interfaces home y descriptores XML, JMS, JTA, JAX-RPC |
| Java EE 5 | 2006 | Las anotaciones sustituyen a la mayoría de los descriptores, EJB 3.0, Java Persistence API (JPA) 1.0, JSF 1.2 |
| Java EE 6 | 2009 | CDI 1.0, EJB 3.1 (vista sin interfaz, singletons), JAX-RS 1.1, Bean Validation, el Web Profile |
| Java EE 7 | 2013 | API simplificada de JMS 2.0, WebSocket, JSON-P, Batch, Concurrency Utilities, @Transactional de JTA |
| Java EE 8 | 2017 | JSON-B, Java EE Security API 1.0, Servlet 4.0 con push de HTTP/2, JAX-RS 2.1 |
| Jakarta EE 8 | 2019 | Las mismas API que Java EE 8, ahora gobernadas por la Eclipse Foundation; paquetes javax.* |
| Jakarta EE 9 / 9.1 | 2020 / 2021 | El espacio de nombres de los paquetes cambia a jakarta.*; se eliminan las API antiguas podadas; soporte de Java 11 |
| Jakarta EE 10 | 2022 | Primera versión con novedades bajo Eclipse: CDI 4.0 con CDI Lite, Core Profile, Servlet 6, Faces 4, Persistence 3.1, REST 3.1 |
| Jakarta EE 11 | 2025 | Java 17 como base mínima, Jakarta Data 1.0, nueva poda de API heredadas |
El cambio de nombre se produjo porque Oracle transfirió en 2017 las especificaciones y el código de referencia a la Eclipse Foundation, pero se quedó con la marca Java. El prefijo de paquete javax formaba parte de esa marca, así que la comunidad no tuvo más remedio que renombrar todos los paquetes. Por eso Jakarta EE 9 es una versión que rompe la compatibilidad sin aportar ninguna novedad: javax.servlet.http.HttpServletRequest pasó a ser jakarta.servlet.http.HttpServletRequest, javax.persistence.Entity pasó a ser jakarta.persistence.Entity, y así con todas las especificaciones. Los espacios de nombres XML de los descriptores de despliegue cambiaron al mismo tiempo, de http://xmlns.jcp.org/xml/ns/javaee a https://jakarta.ee/xml/ns/jakartaee.
Esta guía se centra en Jakarta EE 10 y usa jakarta.* en todo momento. Cuando leas material más antiguo —y la mayor parte del código Java empresarial en producción se escribió para Java EE 6, 7 u 8—, sustituye mentalmente el prefijo: por lo demás, los nombres de clases y métodos son los mismos.
El modelo de capas y contenedores
Jakarta EE describe una aplicación como un conjunto de componentes repartidos en capas lógicas. Las capas son conceptuales: dos de ellas suelen compartir un mismo proceso, y en la mayoría de los despliegues modernos todas las capas del lado servidor viven en una única instancia del servidor de aplicaciones.
- Capa cliente — un navegador, una app móvil, otro servicio que llama a un endpoint REST o un cliente Java de escritorio. Para este último caso la plataforma sigue definiendo un contenedor de cliente de aplicación, aunque hoy apenas se usa.
- Capa web — servlets, páginas Faces, recursos Jakarta REST y endpoints WebSocket. Estos componentes reciben las peticiones, las traducen en llamadas a la capa de negocio y generan la respuesta.
- Capa de negocio — enterprise beans y beans CDI que implementan los casos de uso, se ejecutan dentro de transacciones y aplican las reglas de seguridad.
- Capa de sistemas de información empresarial (EIS) — bases de datos relacionales, brokers de mensajes, mainframes y otros back-ends a los que se llega mediante JDBC, JMS o resource adapters de conectores (JCA).
Lo que convierte esto en algo más que un diagrama de arquitectura por capas es el contenedor: el runtime que aloja un tipo de componente y se sitúa entre el componente y el mundo exterior. Como es el contenedor quien instancia e invoca tu servlet o tu bean, puede:
- inyectar dependencias (
@Inject,@EJB,@PersistenceContext,@Resource) antes de que se ejecute tu código; - iniciar y confirmar una transacción alrededor de cada método de negocio;
- comprobar que quien llama tiene el rol necesario;
- mantener un pool de instancias, gestionar el acceso de los threads y pasivar el estado inactivo;
- aplicar interceptores y callbacks de ciclo de vida (
@PostConstruct,@PreDestroy).
El contenedor web aloja servlets, filtros, listeners, páginas JSP, vistas Faces, recursos REST y endpoints WebSocket. El contenedor de enterprise beans aloja los session beans y los message-driven beans. Ambos comparten el contenedor CDI para la inyección de dependencias y los scopes contextuales, además de servicios de plataforma como el servicio de nombres JNDI, las transacciones JTA y el subsistema de seguridad. La consecuencia práctica: nunca instancies un componente gestionado con new. Un new OrderService() es un objeto normal y corriente, sin campos inyectados, sin transacción y sin contexto de seguridad; solo las instancias creadas por el contenedor los tienen.
Lo que aporta realmente el servidor
Un servidor de aplicaciones es el producto que implementa las especificaciones. Los proveedores compiten en herramientas de administración, clustering, rendimiento y soporte, no en las API contra las que compila tu código. Entre los servicios que todo servidor compatible debe ofrecer están:
| Servicio | Especificación | Para qué lo usas |
|---|---|---|
| Servicio de nombres | JNDI (parte de Java SE) | Buscar por nombre data sources, destinos JMS y beans |
| Transacciones | Jakarta Transactions | Límites ACID declarativos o programáticos, incluido XA entre varios recursos |
| Persistencia | Jakarta Persistence | Mapeo objeto-relacional con un EntityManager gestionado por el contenedor |
| Mensajería | Jakarta Messaging | Colas y topics, message-driven beans |
| Web | Servlet, Pages, Faces, REST, WebSocket | Gestión de peticiones HTTP y renderizado |
| Inyección y scopes | CDI, Dependency Injection | Conectar componentes, scopes de request/session/application, eventos |
| Seguridad | Jakarta Security, Authentication, Authorization | Mecanismos de autenticación, identity stores, comprobación de roles |
| Concurrencia | Jakarta Concurrency | Executors gestionados y tareas programadas que conservan el contexto del contenedor |
| Integración | Connectors (JCA), Mail | Resource adapters para sistemas heredados, envío de correo |
Los recursos como los data sources se configuran en el servidor y se exponen a tu código mediante nombres JNDI, de modo que el mismo WAR funciona en pruebas y en producción con credenciales distintas y sin recompilar. @DataSourceDefinition permite que una aplicación declare su propio data source, algo cómodo para demos pero que mete las credenciales en el código.
import jakarta.annotation.Resource;
import jakarta.enterprise.context.ApplicationScoped;
import javax.sql.DataSource;
@ApplicationScoped
public class HealthCheck {
// El contenedor resuelve este nombre JNDI en el momento de la inyección.
@Resource(lookup = "java:/jdbc/OrdersDS")
DataSource dataSource;
public boolean databaseReachable() {
try (var connection = dataSource.getConnection()) {
return connection.isValid(2);
} catch (Exception e) {
return false;
}
}
}
Perfiles: Platform, Web y Core
No todas las aplicaciones necesitan todo eso, y exigir un servidor de plataforma completa para una pequeña aplicación web fue una de las razones por las que Java EE perdió terreno frente a stacks más ligeros en los años 2000. Desde Java EE 6 la plataforma define perfiles, y Jakarta EE 10 tiene tres:
- Jakarta EE Platform — todo.
- Web Profile — la capa web más lo que una aplicación web típica necesita detrás: Servlet, Pages, Expression Language, Faces, CDI, Bean Validation, Persistence, Transactions, REST, JSON-P/JSON-B, WebSocket, Security, Concurrency (incorporada al perfil en EE 10) y EJB Lite: session beans locales, transacciones gestionadas por el contenedor, interceptores, seguridad declarativa y timers no persistentes, pero sin interfaces remotas, sin message-driven beans y sin JMS.
- Core Profile — nuevo en EE 10 y pensado para frameworks de microservicios y compilación ahead-of-time: CDI Lite, REST, JSON-P, JSON-B, Annotations, Dependency Injection e Interceptors. MicroProfile se construye sobre este perfil.
La elección afecta a tu dependencia de Maven. Compila contra el artefacto de API del perfil al que apuntas y deja que el servidor lo proporcione en tiempo de ejecución:
<dependency>
<groupId>jakarta.platform</groupId>
<artifactId>jakarta.jakartaee-web-api</artifactId> <!-- o jakarta.jakartaee-api para la plataforma completa -->
<version>10.0.0</version>
<scope>provided</scope>
</dependency>
El scope provided importa. Si empaquetas las clases de la API —o, peor aún, una implementación como Hibernate— dentro de un WAR desplegado en un servidor que ya las incluye, tendrás conflictos de carga de clases muy difíciles de diagnosticar.
Empaquetado y despliegue
Las aplicaciones Jakarta EE se despliegan como archivos con una estructura interna fija, y el contenedor lee tanto las anotaciones como los descriptores XML opcionales para descubrir los componentes.
- WAR (web archive) —
WEB-INF/classespara tus clases,WEB-INF/libpara las librerías,WEB-INF/web.xml(opcional desde Servlet 3.0),WEB-INF/beans.xml(opcional en CDI 4, que por defecto descubre los beans anotados),META-INF/persistence.xmldentro declassespara JPA. Desde Java EE 6 un WAR puede contener enterprise beans directamente; es, con diferencia, el empaquetado más habitual hoy en día. - EJB JAR — solo enterprise beans, con un
META-INF/ejb-jar.xmlopcional. - EAR (enterprise archive) — un envoltorio que contiene WAR, EJB JAR, resource adapters (RAR) y librerías compartidas en
lib/, descrito porMETA-INF/application.xml. Úsalo solo cuando de verdad necesites que varios módulos compartan librerías y se desplieguen como una unidad; complica la carga de clases y la mayoría de los equipos ya no lo necesitan.
La cabecera de un web.xml de Servlet 6.0 muestra el espacio de nombres y la versión actuales:
<web-app xmlns="https://jakarta.ee/xml/ns/jakartaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="https://jakarta.ee/xml/ns/jakartaee
https://jakarta.ee/xml/ns/jakartaee/web-app_6_0.xsd"
version="6.0">
<session-config>
<session-timeout>30</session-timeout>
</session-config>
</web-app>
Los descriptores prevalecen sobre las anotaciones, y esa es la forma prevista de cambiar ajustes propios de cada entorno —una restricción de seguridad, un parámetro de inicialización de un servlet, un timeout de sesión— sin tocar el código fuente.
Nombres JNDI portables
Desde Java EE 6 cada session bean recibe nombres JNDI estándar, así que puedes buscarlo desde cualquier sitio sin sintaxis específica del proveedor:
java:global/<app-name>/<module-name>/<bean-name>[!<fully-qualified-interface>]
java:app/<module-name>/<bean-name>
java:module/<bean-name>
Para un bean OrderService en shop.war desplegado de forma independiente, el nombre global es java:global/shop/OrderService. El espacio de nombres java:comp/env contiene las entradas de entorno propias del componente (puntos de inyección @Resource), y java:comp/UserTransaction y java:comp/DefaultDataSource son entradas estándar que todo servidor proporciona. La inyección ha hecho que los lookups explícitos sean poco frecuentes, pero aún te los encontrarás en código antiguo y en clientes remotos.
Elegir un servidor o un runtime
Todos los productos compatibles con Jakarta EE 10 superan el mismo Technology Compatibility Kit, así que el comportamiento de las API es el mismo; las diferencias son operativas.
| Producto | Perfil | Notas |
|---|---|---|
| Eclipse GlassFish 7 | Platform | La implementación compatible usada para ratificar las especificaciones; buena para aprender, menos habitual en producción |
| WildFly (27+) | Platform | El servidor comunitario de Red Hat, base de JBoss EAP; ofrece además un feature pack "preview" y empaquetado como bootable JAR |
| Open Liberty | Platform | El runtime modular de IBM; las funcionalidades se activan por aplicación, arranque rápido, base de WebSphere Liberty |
| Payara Server / Micro | Platform | Derivado de GlassFish con soporte comercial; Payara Micro ejecuta un WAR desde la línea de comandos |
| Apache TomEE 10 | Variantes Web Profile / Platform | Tomcat más OpenEJB, OpenJPA/Hibernate, ActiveMQ, CXF |
| Apache Tomcat 10.1 / Jetty 12 | Solo Servlet, Pages, EL, WebSocket | No son servidores Jakarta EE; el resto lo aportas tú como librerías |
En la práctica, la elección depende de lo que tu equipo de operaciones ya ejecuta y parchea, de si necesitas soporte comercial (JBoss EAP, WebSphere Liberty y Payara Enterprise son las ediciones con soporte de WildFly, Open Liberty y Payara) y de cómo despliegas: todos estos servidores se pueden incluir en una imagen de contenedor junto con la aplicación, y el bootable JAR de WildFly o Payara Micro generan un único ejecutable, muy parecido a un jar de Spring Boot. Si solo usas servlets, JPA y REST, un runtime Web Profile —o Tomcat más librerías— puede ser la respuesta pragmática.
Dónde encajan Spring y MicroProfile
Spring nació en 2003 como reacción a EJB 2.x, cuyas interfaces home, excepciones checked obligatorias y descriptores XML convertían lo sencillo en laborioso. Java EE 5 y 6 absorbieron la mayoría de las ideas de Spring —anotaciones, inyección de dependencias, componentes como objetos simples— en EJB 3 y CDI. Hoy la relación es más simbiótica que competitiva: Spring Framework 6 y Spring Boot 3 dependen de las API de Jakarta EE 9+ para la capa de servlets, JPA, Bean Validation, JMS y JTA, y simplemente aportan su propio contenedor en lugar de depender de un servidor de aplicaciones. Si entiendes las especificaciones, entiendes buena parte de lo que Spring autoconfigura; el tema de Spring cubre esa parte.
MicroProfile es un conjunto hermano de especificaciones de Eclipse que añade sobre el Core Profile lo que necesitan los servicios cloud-native —configuración, health checks, métricas, OpenAPI, un cliente REST, tolerancia a fallos, propagación de JWT— y lo implementan Open Liberty, Payara, WildFly, Quarkus y Helidon. Lo que sepas de CDI y Jakarta REST te sirve en todos ellos.
Migrar de javax a jakarta
Como el renombrado de Jakarta EE 9 es mecánico, las herramientas pueden hacer la mayor parte del trabajo:
Actualiza el runtime y la dependencia de la API
Pasa a un servidor Jakarta EE 9.1+ (o Tomcat 10.1+, Jetty 11+) y cambia la dependenciajakarta.jakartaee-api(o las de cada especificación) a una versión 9.1 o 10.Reescribe imports y descriptores
Ejecuta Eclipse Transformer o una receta de OpenRewrite sobre el árbol de código fuente. Reescriben los importsjavax.*de los paquetes afectados, los espacios de nombres XML y nombres de propiedades comojavax.persistence.jdbc.url, sin tocar los paquetes de Java SE comojavax.sqlyjavax.naming, que nunca se movieron.Actualiza todas las librerías que usan las API
Hibernate 6, Jersey 3, RESTEasy 6, Mojarra 4, EclipseLink 4 y Spring 6 son las líneas con el espacio de nombres de Jakarta. Una sola dependencia transitiva que siga importandojavax.servletfallará en tiempo de ejecución conNoClassDefFoundError, así que revisa el árbol de dependencias.Busca API eliminadas
Servlet 6.0 eliminó tipos declarados obsoletos hacía mucho, comoSingleThreadModelyHttpSessionContext; Faces 4.0 eliminó@ManagedBeany las vistas JSP; Enterprise Beans 4.0 prescindió de los entity beans de EJB 2.x y de los endpoints JAX-RPC. El código que los usa hay que reescribirlo, no basta con cambiar los imports.
La guía de migración de Tomcat describe una variante del mismo enfoque: Tomcat 10 puede transformar al vuelo los WAR heredados, lo que te da margen pero no debería ser el plan a largo plazo.
Errores comunes
- Empaquetar una implementación que el servidor ya incluye. Meter
hibernate-coreo una implementación de Jakarta REST dentro de un WAR para WildFly o Liberty produce dos copias de las mismas clases en class loaders distintos y errores comoClassCastException: OrderEntity cannot be cast to OrderEntity. Usa el scopeprovidedpara las API y deja que el servidor aporte las implementaciones, salvo que el servidor documente cómo sustituir su versión. - Mezclar artefactos
javaxyjakarta. Un proyecto migrado a medias compila y luego falla en el despliegue. Revisa el árbol de dependencias, no solo tus propios imports. - Tratar las capas como físicas. Separar la capa web y la de negocio en servidores distintos conectados por llamadas remotas a enterprise beans estuvo de moda en la época de J2EE, y casi siempre es más lento y más difícil de operar que un único proceso. Mantenlas juntas salvo que tengas mediciones que justifiquen lo contrario.
- Configurar los recursos en la aplicación. Las URL de conexión fijadas en
persistence.xmlo en@DataSourceDefinitionfuncionan en un portátil y luego filtran credenciales al control de versiones. Define los data sources y los destinos JMS en el servidor y haz referencia a ellos por su nombre JNDI. - Usar un EAR por costumbre. Un único WAR es más fácil de construir, desplegar y depurar. Recurre a un EAR solo cuando tengas varios módulos que deban versionarse juntos.
Ideas clave
- Jakarta EE es un conjunto de especificaciones; un servidor de aplicaciones las implementa y ejecuta tus componentes dentro de contenedores que inyectan dependencias, gestionan transacciones y aplican la seguridad.
- El linaje es J2EE, luego Java EE 5–8, luego Jakarta EE 8 (mismas API), 9 (
javax.*pasa ajakarta.*, sin novedades), 10 (primera versión con novedades) y 11. - Jakarta EE 10 define tres perfiles —Platform, Web y Core— para que apuntes solo a lo que necesitas; el Web Profile cubre la mayoría de las aplicaciones web.
- Compila contra el artefacto
jakarta.jakartaee-apiojakarta.jakartaee-web-apicon scopeprovidedy deja que el servidor aporte las implementaciones. - Empaqueta como WAR siempre que puedas; usa nombres JNDI portables
java:globaly recursos definidos en el servidor en lugar de configuración fija en el código. - GlassFish, WildFly, Open Liberty, Payara y TomEE son implementaciones compatibles; Tomcat y Jetty solo implementan la familia de Servlet.
- Spring y MicroProfile se basan en las mismas especificaciones, así que el conocimiento se transfiere en ambos sentidos.
Fuentes y lecturas recomendadas
Preguntas frecuentes
- ¿Jakarta EE es lo mismo que Java EE?
- Es la continuación de Java EE bajo la Eclipse Foundation. Jakarta EE 8 es funcionalmente idéntico a Java EE 8 y sigue usando los paquetes javax.*. A partir de Jakarta EE 9 los paquetes pasaron a jakarta.*, y por eso las librerías y servidores escritos para Java EE 8 no funcionan sin cambios en un servidor Jakarta EE 9 o posterior.
- ¿Necesito un servidor de aplicaciones o me basta con Tomcat?
- Tomcat y Jetty solo implementan las especificaciones de Servlet, JSP, EL y WebSocket. Si necesitas que el propio contenedor te dé CDI, JPA, JTA, JMS, enterprise beans o Jakarta REST, usa un servidor Web Profile o de Platform completa como WildFly, Open Liberty, Payara, GlassFish o TomEE. Frameworks como Spring Boot traen sus propias implementaciones de varias de esas especificaciones y por eso pueden ejecutarse sobre Tomcat.
- ¿Qué diferencia hay entre el Web Profile y la Platform completa?
- El Web Profile es un subconjunto pensado para aplicaciones web: servlets, Faces, CDI, JPA, JTA, Bean Validation, Jakarta REST, JSON, WebSocket, Security, Concurrency y un conjunto reducido de funcionalidades de enterprise beans llamado EJB Lite. La Platform completa añade mensajería (JMS), conectores, Mail, Batch, beans remotos y message-driven, entre otras cosas. El Core Profile, nuevo en Jakarta EE 10, es aún más pequeño y está orientado a runtimes de microservicios.
- ¿Un proyecto nuevo debería usar Jakarta EE o Spring Boot?
- Ambas opciones son viables. Jakarta EE te da estándares neutrales respecto al proveedor, un runtime gestionado y menos código de framework en tu aplicación; Spring Boot te da un ecosistema más amplio, un ritmo de versiones más rápido y un empaquetado autónomo más sencillo. El propio Spring 6 se basa en las API de Jakarta EE 9+ (Servlet, JPA, Bean Validation), así que las especificaciones que aprendes aquí se aplican directamente.