协作头脑风暴
brainstorming
在实现前澄清需求、探索设计选择,并形成清晰的开发方向。
jakarta-ee【开发与工程】Jakarta EE platform reference covering specifications, APIs, reference implementations, build configuration, and architecture patterns for enterprise Java development.
独立 GitHub 快照 · 自动分类 · 以原作者文档为准
name: jakarta-ee description: Jakarta EE platform reference covering specifications, APIs, reference implementations, build configuration, and architecture patterns for enterprise Java development. license: MIT compatibility: opencode metadata: version: "1.0.0" domain: cncf triggers: jakarta ee, jakarta-ee, enterprise java, servlet, jax-rs, cdi, jsf, ejb javanamespace migration archetypes:
Provides architecture guidance, specification references, and implementation patterns for building enterprise Java applications on the Jakarta EE platform. Acts as a reference for selecting containers, configuring build systems, wiring CDI beans, JAX-RS resources, JPA entities, and JSF pages into production-grade deployments.
jakarta.platform:jakarta.jakartaee-api dependency — never javax.* coordinatesJakarta EE is the open-source evolution of Java EE (formerly J2EE), governed by the Eclipse Foundation. It provides standardized APIs for enterprise application development: servlets, REST services, dependency injection, persistence, messaging, security, and more.
Use Jakarta EE when:
Consider Spring Boot instead when:
Consider plain Java SE when:
Use this skill when:
Avoid this skill for:
Select Container Runtime — Choose the appropriate Jakarta EE server based on requirements:
Configure Build Dependencies — Declare jakarta.platform:jakarta.jakartaee-api with <scope>provided</scope> in pom.xml. For fine-grained control, declare individual jakarta.* API dependencies instead of the umbrella artifact. Never add implementation JARs as compile-time dependencies.
Checkpoint: Run mvn dependency:tree to confirm no javax.* artifacts appear in the transitive dependency graph.
Wire CDI Components — Define beans with appropriate scopes (@RequestScoped for web-layer, @ApplicationScoped for stateless services). Configure beans.xml with bean-discovery-mode="annotated" or "all". Add producer methods for cross-cutting concerns (datasources, interceptors).
Checkpoint: Ensure every injected dependency has exactly one qualifying bean or is wrapped in Optional.
Configure Persistence Layer — Define persistence.xml with JTA-managed entity manager factory. Annotate service methods with @TransactionAttribute (REQUIRED for writes, SUPPORTS for reads). Never annotate repository/DAO methods with transaction attributes — they inherit from the caller.
Checkpoint: Verify all entity classes have @Entity, @Table, and a default no-arg constructor required by JPA providers.
Deploy as Packaged WAR — Package the application using Maven Assembly or Shade plugin for uber-jars, or use container-native deployment (copy .war to config/apps/ for OpenLiberty). For Kubernetes, create a ConfigMap with the artifact and mount it into the container. Checkpoint: Test deployment on a local Jakarta EE reference server before pushing to CI/CD pipeline artifacts.
Jakarta EE applications follow a layered architecture where the container provides lifecycle management for each component type:
┌─────────────────────────────────────────────────────────┐
│ Client Layer │
│ (Browser, Mobile, REST Consumer) │
└───────────────────────┬─────────────────────────────────┘
│ HTTP / JMS
┌───────────────────────▼─────────────────────────────────┐
│ Web Tier │
│ ┌────────────┐ ┌──────────┐ ┌─────────────────────┐ │
│ │ JSF │ │ JAX-RS │ │ Servlet Filter Chain│ │
│ │ (Faces) │ │ Resources│ │ Security / Logging │ │
│ └─────┬──────┘ └────┬─────┘ └──────────┬──────────┘ │
│ │ │ │ │
│ ┌─────▼──────────────▼────────────────────▼──────────┐ │
│ │ CDI Context & DI Container │ │
│ │ (Wires beans, manages scopes: @RequestScoped, │ │
│ │ @SessionScoped, @ApplicationScoped, @Conversation)│ │
│ └─────────────────────┬──────────────────────────────┘ │
└────────────────────────┼─────────────────────────────────┘
│ JPA / JDBC / JMS
┌────────────────────────▼─────────────────────────────────┐
│ Persistence Tier │
│ ┌────────────┐ ┌──────────┐ ┌─────────────────────┐ │
│ │ JPA │ │ JTA │ │ Connection Pool │ │
│ │ (Hibernate)│ │ Tx Mgr │ │ (HikariCP/Tomcat) │ │
│ └─────┬──────┘ └──────────┘ └─────────────────────┘ │
│ │ │
│ ┌─────▼──────────────────────────────────────────────┐ │
│ │ Database (JDBC) │ │
│ └────────────────────────────────────────────────────┘ │
└──────────────────────────────────────────────────────────┘
Background Tier: Message-Driven Beans (JMS consumers)
Scheduled Tasks @Schedule / Concurrency Utils
A Jakarta EE web application packaged as a WAR follows this standard layout:
myapp.war
├── META-INF/
│ ├── MANIFEST.MF
│ └── context.xml # Container-specific configuration
├── WEB-INF/
│ ├── web.xml # Deployment descriptor (optional in EE 10+)
│ ├── beans.xml # CDI configuration
│ ├── persistence.xml # JPA entity manager factory config
│ ├── faces-config.xml # JSF navigation config (if used)
│ └── classes/
│ └── com/example/
│ ├── resource/
│ │ └── MyResource.class # JAX-RS
│ ├── service/
│ │ ├── UserService.class # CDI @ApplicationScoped
│ │ └── UserDTO.class
│ ├── repository/
│ │ └── UserRepository.class # JPA DAO
│ ├── entity/
│ │ └── UserEntity.class # @Entity
│ └── interceptor/
│ └── LoggingInterceptor.class
├── resources/
│ └── application.properties
└── index.html # Static web content
The build system declares the Jakarta EE API dependency and compiles against it. The actual implementation is provided by the runtime container at deployment time:
<!-- pom.xml — Jakarta EE 10 Web Profile -->
<dependency>
<groupId>jakarta.platform</groupId>
<artifactId>jakarta.jakartaee-api</artifactId>
<version>10.0.0</version>
<scope>provided</scope>
</dependency>
<!-- Specific API dependencies for precise control -->
<dependency>
<groupId>jakarta.servlet</groupId>
<artifactId>jakarta.servlet-api</artifactId>
<version>6.1.0</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>jakarta.enterprise</groupId>
<artifactId>jakarta.enterprise.cdi-api</artifactId>
<version>4.1.0</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>jakarta.ws.rs</groupId>
<artifactId>jakarta.ws.rs-api</artifactId>
<version>3.1.0</version>
<scope>provided</scope>
</dependency>
REST API request lifecycle:
@GET, @POST, etc.@Inject fields (services, repositories)| Specification | Spec Number | Maven Group ID | Brief Description |
|---|---|---|---|
| Servlet | JSR 369 / 6.0 | jakarta.servlet | HTTP request/response processing, filter chains, session management |
| JAX-RS | JSR 388 / 3.1 | jakarta.ws.rs | RESTful web services with annotations (@Path, @GET, @POST) |
| CDI | JSR 365 / 4.0 | jakarta.enterprise | Dependency injection, contextual lifecycle, events, interceptors, alternatives |
| JPA | JSR 338 / 3.1 | jakarta.persistence | Object-relational mapping, entity management, JPQL queries |
| JSF | JSR 372 / 4.0 | jakarta.faces | Component-based web UI framework with server-side state management |
| EJB | JSR 383 / 4.0 | jakarta.ejb | Enterprise JavaBeans — session beans, message-driven beans, timers |
| Bean Validation | JSR 380 / 3.0 | jakarta.validation | Declarative constraint validation (@NotNull, @Size, custom validators) |
| JSON-P | JSR 374 / 3.1 | jakarta.json | Streaming (JsonReader/Writer) and object model (JsonObject) JSON processing |
| JSON-B | JSR 367 / 2.0 | jakarta.json.bind | Java-to-JSON binding with annotations (@JsonbProperty, type adapters) |
| JMS | JSR 238 / 3.1 | jakarta.jms | Messaging API — queues, topics, producers, consumers, message listeners |
| JTA | JSR 907 / 2.1 | jakarta.transaction | Distributed transaction management, @Transactional, UserTransaction |
| WebSocket | JSR 368 / 3.1 | jakarta.websocket | Full-duplex bidirectional communication over a single TCP connection |
| Interceptors | JSR 318 / 3.0 | jakarta.interceptor | AOP-style cross-cutting concerns — logging, security, transactions |
| Security | JSR 375 / 3.1 | jakarta.security.enterprise | Programmatic and declarative authentication, identity stores |
| Concurrency Utils | JSR 236 / 2.0 | jakarta.concurrent | Managed thread pools, async execution, managed executors |
| JSR 337 / 2.1 | jakarta.mail | Email sending/receiving API (Eclipse Mail) |
| Server | Description | Best Used For | Notable Features |
|---|---|---|---|
| WildFly (JBoss) | Red Hat's reference implementation of Jakarta EE Full Platform | Large enterprises, microservices with full EE spec support | WildFly Swarm/JAR overlay for uber-jar distribution, Kubernetes operators, extensive management CLI |
| Payara Server | GlassFish-based server with commercial support options | Migration from Oracle/Sun GlassFish, high-availability clusters | Auto-deployment of MicroProfile, built-in clustering (Hazelcast), Payara Micro for lightweight deployment |
| OpenLiberty (IBM) | Modular, cloud-native Jakarta EE server | Containerized deployments, fast startup in Kubernetes | Feature-based activation (only load what you need), rapid restart, OpenTelemetry built-in, Liberty Profile |
| TomEE | Apache Tomcat + EJB/CDI/JAX-RS addons | Lightweight deployments, teams already using Tomcat | Smallest footprint of full EE servers, pluggable profiles (Plus, PlusJSR356, JavaEE), Maven plugin for assembly |
| Quarkus | Cloud-native Java framework with Jakarta EE compatibility | Native compilation with GraalVM, Kubernetes-first architecture | Super-fast startup (< 1s), dev mode with hot reload, extension-based architecture, not a full container but supports most specs |
MicroProfile sits on top of Jakarta EE as a cloud-native overlay. It adds specifications focused on microservice operational concerns:
<!-- Enable MicroProfile features in pom.xml -->
<dependency>
<groupId>org.eclipse.microprofile</groupId>
<artifactId>microprofile</artifactId>
<version>6.1</version>
<type>pom</type>
<scope>provided</scope>
</dependency>
On WildFly, install the MicroProfile feature: feature-manager add microprofile-6.1
On OpenLiberty, add features to server.xml: <feature>microprofile-6.1</feature>
JPA with JTA provides transaction-scoped persistence contexts:
@Stateless
public class UserService {
@PersistenceContext(unitName = "default")
private EntityManager entityManager;
@TransactionAttribute(TransactionAttributeType.REQUIRED)
public UserDTO createUser(UserCreateRequest request) {
// Transaction begins before this line, commits after return
UserEntity entity = new UserEntity();
entity.setUsername(request.username());
entity.setEmail(request.email());
entityManager.persist(entity);
// Convert to DTO for layer boundary separation
return mapToDto(entity);
}
}
Jakarta EE containers provide managed connection factories and resource adapters:
@Stateless
public class OrderMessageProcessor implements MessageListener {
@Resource(lookup = "java:comp/env/jms/OrderConnectionFactory")
private ConnectionFactory connectionFactory;
@PersistenceContext(unitName = "orderDatabase")
private EntityManager entityManager;
@Override
public void onMessage(Message message) {
try {
if (message instanceof TextMessage textMsg) {
Order order = parseOrder(textMsg.getText());
processOrder(order);
// JTA transaction commits with the EJB method completion
}
} catch (JMSException e) {
LOGGER.error("Failed to process JMS message", e);
throw new RuntimeException("Message processing failed", e);
}
}
private void processOrder(Order order) {
OrderEntity entity = new OrderEntity();
entity.setOrderId(order.id());
entity.setStatus("PROCESSING");
entityManager.persist(entity);
}
}
Complete deployment for a Jakarta EE application on OpenLiberty:
# kubernetes/deployment.yaml — Jakarta EE app on OpenLiberty
apiVersion: apps/v1
kind: Deployment
metadata:
name: enterprise-app
labels:
app: enterprise-app
version: v1.0.0
spec:
replicas: 3
selector:
matchLabels:
app: enterprise-app
template:
metadata:
labels:
app: enterprise-app
annotations:
prometheus.io/scrape: "true"
prometheus.io/port: "9080"
spec:
containers:
- name: enterprise-app
image: openliberty/wlp-javaee10:24.0.0.12-full-jre
ports:
- containerPort: 9080
name: http
- containerPort: 9443
name: https
- containerPort: 9090
name: mgmt
volumeMounts:
- name: app-deployments
mountPath: /config/configDropins/defaults/autoscale.xml
subPath: autoscale.xml
- name: myapp
mountPath: /config/apps/myapp.war
subPath: myapp.war
resources:
requests:
memory: "512Mi"
cpu: "250m"
limits:
memory: "1Gi"
cpu: "500m"
livenessProbe:
httpGet:
path: /liveness
port: 9080
initialDelaySeconds: 30
periodSeconds: 10
readinessProbe:
httpGet:
path: /readiness
port: 9080
initialDelaySeconds: 20
periodSeconds: 5
volumes:
- name: myapp
configMap:
name: app-artifacts
- name: app-deployments
configMap:
name: openliberty-config
---
# Service exposing the application
apiVersion: v1
kind: Service
metadata:
name: enterprise-app-service
spec:
selector:
app: enterprise-app
ports:
- port: 80
targetPort: 9080
name: http
type: ClusterIP
package com.example.resource;
import jakarta.inject.Inject;
import jakarta.persistence.EntityManager;
import jakarta.persistence.PersistenceContext;
import jakarta.transaction.Transactional;
import jakarta.validation.Valid;
import jakarta.validation.constraints.Email;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.Size;
import jakarta.ws.rs.*;
import jakarta.ws.rs.core.MediaType;
import jakarta.ws.rs.core.Response;
import java.util.List;
import java.util.stream.Collectors;
@Path("users")
@Produces(MediaType.APPLICATION_JSON)
@Consumes(MediaType.APPLICATION_JSON)
public class UserResource {
@Inject
private UserService userService;
@GET
public Response getAllUsers() {
List<UserDTO> users = userService.findAll();
return Response.ok(users).build();
}
@GET
@Path("{id}")
public Response getUserById(@PathParam("id") Long id) {
UserDTO user = userService.findById(id);
if (user == null) {
return Response.status(Response.Status.NOT_FOUND)
.entity(new ErrorResponse("User not found: " + id)).build();
}
return Response.ok(user).build();
}
@POST
@Transactional
public Response createUser(@Valid UserCreateRequest request) {
// Bean Validation intercepts here — @Valid triggers constraint checks
// on the request body before this method body executes
UserDTO created = userService.create(request);
return Response.status(Response.Status.CREATED)
.location(java.net.URI.create("/users/" + created.id()))
.entity(created).build();
}
@PUT
@Path("{id}")
@Transactional
public Response updateUser(@PathParam("id") Long id, @Valid UserUpdateRequest request) {
UserDTO updated = userService.update(id, request);
if (updated == null) {
return Response.status(Response.Status.NOT_FOUND).build();
}
return Response.ok(updated).build();
}
@DELETE
@Path("{id}")
@Transactional
public Response deleteUser(@PathParam("id") Long id) {
boolean deleted = userService.delete(id);
if (!deleted) {
return Response.status(Response.Status.NOT_FOUND).build();
}
return Response.noContent().build();
}
// Validation request DTO — Bean Validation annotations are enforced by @Valid
public record UserCreateRequest(
@NotBlank(message = "Username is required")
@Size(min = 3, max = 50, message = "Username must be between 3 and 50 characters")
String username,
@NotBlank(message = "Email is required")
@Email(message = "Must be a valid email address")
String email,
@NotBlank(message = "Password is required")
@Size(min = 8, message = "Password must be at least 8 characters")
String password
) {}
public record ErrorResponse(String message) {}
}
package com.example.service;
import jakarta.enterprise.context.ApplicationScoped;
import jakarta.inject.Inject;
import jakarta.persistence.EntityManager;
import jakarta.persistence.NoResultException;
import jakarta.persistence.TypedQuery;
import jakarta.persistence.criteria.CriteriaBuilder;
import jakarta.persistence.criteria.CriteriaDelete;
import jakarta.persistence.criteria.CriteriaQuery;
import jakarta.persistence.criteria.Root;
import jakarta.transaction.Transactional;
@ApplicationScoped
@Transactional
public class UserService {
@Inject
private EntityManager entityManager;
public UserDTO findById(Long id) {
var entity = entityManager.find(UserEntity.class, id);
return entity != null ? toDto(entity) : null;
}
public java.util.List<UserDTO> findAll() {
CriteriaBuilder cb = entityManager.getCriteriaBuilder();
CriteriaQuery<UserEntity> query = cb.createQuery(UserEntity.class);
Root<UserEntity> root = query.from(UserEntity.class);
query.select(root).orderBy(cb.asc(root.get("username")));
TypedQuery<UserEntity> typedQuery = entityManager.createQuery(query);
return typedQuery.getResultStream().map(this::toDto)
.collect(java.util.stream.Collectors.toList());
}
public UserDTO create(UserResource.UserCreateRequest request) {
// Check uniqueness before persisting
CriteriaBuilder cb = entityManager.getCriteriaBuilder();
CriteriaQuery<Long> countQuery = cb.createQuery(Long.class);
Root<UserEntity> root = countQuery.from(UserEntity.class);
countQuery.select(cb.count(root))
.where(cb.equal(root.get("username"), request.username()));
Long existingCount = entityManager.createQuery(countQuery).getSingleResult();
if (existingCount > 0) {
throw new IllegalStateException("Username already exists: " + request.username());
}
UserEntity entity = new UserEntity();
entity.setUsername(request.username());
entity.setEmail(request.email());
entity.setPasswordHash(hashPassword(request.password()));
entityManager.persist(entity);
entityManager.flush(); // Ensure ID is assigned
return toDto(entity);
}
public boolean delete(Long id) {
CriteriaBuilder cb = entityManager.getCriteriaBuilder();
CriteriaDelete<UserEntity> delete = cb.createCriteriaDelete(UserEntity.class);
Root<UserEntity> root = delete.from(UserEntity.class);
delete.where(cb.equal(root.get("id"), id));
int affected = entityManager.createQuery(delete).executeUpdate();
return affected > 0;
}
private String hashPassword(String rawPassword) {
// In production, use BCrypt or Argon2 — shown here for example completeness
return java.security.MessageDigest.getInstance("SHA-256")
.digest(rawPassword.getBytes(java.nio.charset.StandardCharsets.UTF_8))
.map(b -> String.format("%02x", b))
.reduce("", String::concat);
}
private UserDTO toDto(UserEntity entity) {
return new UserDTO(
entity.getId(),
entity.getUsername(),
entity.getEmail()
// Password hash is never exposed in the DTO
);
}
public record UserDTO(Long id, String username, String email) {}
}
package com.example.entity;
import jakarta.persistence.*;
import jakarta.validation.constraints.Email;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.Size;
import java.time.LocalDateTime;
@Entity
@Table(name = "app_users",
uniqueConstraints = @UniqueConstraint(columnNames = "username"))
public class UserEntity {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@NotBlank(message = "Username is required")
@Size(min = 3, max = 50)
@Column(nullable = false, unique = true, length = 50)
private String username;
@Email
@NotBlank(message = "Email is required")
@Column(nullable = false, length = 120)
private String email;
@NotBlank
@Size(min = 8)
@Column(name = "password_hash", nullable = false)
private String passwordHash;
@Column(name = "created_at")
private LocalDateTime createdAt = LocalDateTime.now();
// Default constructor required by JPA
protected UserEntity() {}
public UserEntity(String username, String email, String passwordHash) {
this.username = username;
this.email = email;
this.passwordHash = passwordHash;
}
public Long getId() { return id; }
public void setId(Long id) { this.id = id; }
public String getUsername() { return username; }
public void setUsername(String username) { this.username = username; }
public String getEmail() { return email; }
public void setEmail(String email) { this.email = email; }
public String getPasswordHash() { return passwordHash; }
public void setPasswordHash(String passwordHash) { this.passwordHash = passwordHash; }
public LocalDateTime getCreatedAt() { return createdAt; }
}
| Pitfall | Cause | Prevention |
|---|---|---|
| javax vs jakarta namespace confusion | Mixing old javax.servlet imports with new server or vice versa | Use jakarta.* exclusively; verify all transitive dependencies declare Jakarta namespaces |
| CDI proxy limitations | @RequestScoped proxies cannot be used in static contexts or when serializing | Never call .getClass() on a CDI bean expecting the target type — use interfaces and inject at runtime |
| Transaction boundary misconfiguration | Placing @Transactional on repository methods instead of service layer causes nested transaction overhead | Annotate service/manager methods only; repositories remain non-transactional and rely on the caller's context |
| JSF state management bloat | Server-side view state stored in HTTP session grows large for pages with many components | Use client-side state saving (<param><name>javax.faces.STATE_SAVING_METHOD</name><value>client</value></param>) or switch to RESTeasy + JSON for APIs |
| Missing beans.xml | CDI scanning fails if beans.xml is absent in a module that requires explicit bean discovery | Include an empty <beans/> descriptor in WEB-INF/beans.xml when using bean-discovery-mode="all" |
| Memory pressure on large WARs | Heavy JSF pages with many components, unlimited session scopes holding entities | Profile heap usage with Eclipse MAT; implement lazy loading (FetchType.LAZY) for all entity associations |
jakarta.* namespace exclusively — never import javax.* packages in new codebeans.xml with bean-discovery-mode="annotated" or "all" for CDI modules@NotNull, @Size) on DTOs passed to JAX-RS resourcesjavax.* and jakarta.* dependencies in the same application — this causes class loader conflicts@Stateless or @Singleton EJB annotations for everything — prefer CDI @ApplicationScoped for stateless services.war archives for performance and securityWhen configuring or reviewing a Jakarta EE application, produce:
pom.xml dependency declarations with scope annotations| Skill | Purpose |
|---|---|
microprofile | Cloud-native overlay specifications (config, health, metrics) that run on top of Jakarta EE |
jakarta-migration | Step-by-step process for migrating Java EE 8 (javax.*) applications to Jakarta EE 9+ |
Authoritative documentation links for the Jakarta EE platform.