인터페이스는 현지화되어 있으며, Skill 소개와 설명 문서는 선택한 언어로 AI가 자동 번역합니다. 코드·명령어·경로·전문 용어는 원문을 유지하며 원작자 문서가 우선합니다.
Skills 둘러보기
개발 및 엔지니어링 · SKILL.md

jakarta-ee

jakarta-ee

Jakarta EE platform reference covering specifications, APIs, reference implementations, build configuration, and architecture patterns for enterprise Java development.

Independent GitHub snapshot · Automated classification · Original documentation governs

Skill 문서

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:

  • educational
  • strategic anti_triggers:
  • brainstorming
  • vague ideation
  • non-containerized architecture response_profile: verbosity: medium directive_strength: low abstraction_level: strategic role: reference scope: infrastructure output-format: manifests content-types:
  • guidance
  • examples
  • do-dont
  • config related-skills: microprofile, jakarta-migration

Jakarta EE Platform

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.

TL;DR Checklist

  • Choose the right container (WildFly for full EE, Payara for GlassFish compatibility, OpenLiberty for modular cloud-native)
  • Use jakarta.platform:jakarta.jakartaee-api dependency — never javax.* coordinates
  • Configure CDI producers and beans.xml before wiring cross-cutting concerns
  • Set JTA transaction boundaries at the service/manager layer, not on individual DAO methods
  • Include a complete WAR structure with web.xml namespace declarations matching jakarta.* schema

Purpose and Use Cases

Jakarta 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:

  • Building large-scale enterprise applications requiring certified platform compatibility
  • Migrating from legacy Java EE / GlassFish deployments
  • Needing transaction management, connection pooling, and JMS out of the box
  • Targeting multi-vendor interoperability (any Jakarta EE 10/11 server runs the same WAR)
  • Building services that integrate with MicroProfile for cloud-native capabilities

Consider Spring Boot instead when:

  • The team has deep Spring ecosystem expertise and no legacy Java EE investments
  • You need rapid prototyping with embedded servers and auto-configuration
  • The deployment model is exclusively Kubernetes with containerized microservices
  • You require non-standard or experimental features before they reach Jakarta EE spec status

Consider plain Java SE when:

  • The application has no enterprise concerns (no transactions, persistence, or web layer)
  • Building CLI tools, background daemons, or simple HTTP servers using frameworks like Spark or javalin
  • The team prefers minimal dependencies and explicit configuration over platform conveniences

When to Use

Use this skill when:

  • Architecting a new enterprise Java application on a Jakarta EE 10 or 11 server
  • Migrating a legacy Java EE 8 (GlassFish, JBoss EAP 7.x) application to Jakarta EE 9+
  • Selecting the right container runtime (WildFly full platform vs. OpenLiberty modular vs. Payara)
  • Configuring CDI beans, JAX-RS resources, and JPA persistence layers with proper transaction boundaries
  • Deploying a WAR-based application to Kubernetes using containerized Jakarta EE servers
  • Integrating MicroProfile specifications (health, metrics, config) on top of the Jakarta EE platform
  • Setting up database connection pooling and JTA distributed transactions in a multi-datasource environment

When NOT to Use

Avoid this skill for:

  • Starting a greenfield microservice project — Spring Boot or Quarkus may be more productive choices
  • Building simple HTTP services without enterprise concerns — consider lightweight frameworks like Spark Java, Javalin, or Helidon SE
  • Containerizing Node.js, Python, or Go applications — Jakarta EE is exclusively for the Java ecosystem

Core Workflow

  1. Select Container Runtime — Choose the appropriate Jakarta EE server based on requirements:

    • Full platform compliance (EJB, JMS, Batch): WildFly or Payara Server
    • Cloud-native with modular features and fast startup: OpenLiberty
    • Small footprint with existing Tomcat infrastructure: TomEE
    • Native GraalVM compilation: Quarkus (Jakarta EE compatibility mode) Checkpoint: Verify the chosen server supports at least Jakarta EE 10 Web Profile for the target deployment environment.
  2. 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.

  3. 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.

  4. 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.

  5. 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.


Architecture Design Patterns

Jakarta EE Component Model

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

WAR Structure

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

Key Build Configuration

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>

Component Interaction Flow

REST API request lifecycle:

  1. HTTP request enters the container (WildFly/Payara/OpenLiberty)
  2. CDI creates or retrieves a request-scoped proxy for the JAX-RS resource class
  3. The JAX-RS runtime dispatches to the method annotated with @GET, @POST, etc.
  4. CDI dependency injection populates @Inject fields (services, repositories)
  5. The service layer invokes JPA repository methods
  6. JTA coordinates the transaction — begins before first DB call, commits after response
  7. Response serializes to JSON/XML via Jakarta JSON-P or JSON-B

Key Specifications Table

SpecificationSpec NumberMaven Group IDBrief Description
ServletJSR 369 / 6.0jakarta.servletHTTP request/response processing, filter chains, session management
JAX-RSJSR 388 / 3.1jakarta.ws.rsRESTful web services with annotations (@Path, @GET, @POST)
CDIJSR 365 / 4.0jakarta.enterpriseDependency injection, contextual lifecycle, events, interceptors, alternatives
JPAJSR 338 / 3.1jakarta.persistenceObject-relational mapping, entity management, JPQL queries
JSFJSR 372 / 4.0jakarta.facesComponent-based web UI framework with server-side state management
EJBJSR 383 / 4.0jakarta.ejbEnterprise JavaBeans — session beans, message-driven beans, timers
Bean ValidationJSR 380 / 3.0jakarta.validationDeclarative constraint validation (@NotNull, @Size, custom validators)
JSON-PJSR 374 / 3.1jakarta.jsonStreaming (JsonReader/Writer) and object model (JsonObject) JSON processing
JSON-BJSR 367 / 2.0jakarta.json.bindJava-to-JSON binding with annotations (@JsonbProperty, type adapters)
JMSJSR 238 / 3.1jakarta.jmsMessaging API — queues, topics, producers, consumers, message listeners
JTAJSR 907 / 2.1jakarta.transactionDistributed transaction management, @Transactional, UserTransaction
WebSocketJSR 368 / 3.1jakarta.websocketFull-duplex bidirectional communication over a single TCP connection
InterceptorsJSR 318 / 3.0jakarta.interceptorAOP-style cross-cutting concerns — logging, security, transactions
SecurityJSR 375 / 3.1jakarta.security.enterpriseProgrammatic and declarative authentication, identity stores
Concurrency UtilsJSR 236 / 2.0jakarta.concurrentManaged thread pools, async execution, managed executors
MailJSR 337 / 2.1jakarta.mailEmail sending/receiving API (Eclipse Mail)

Reference Implementations

ServerDescriptionBest Used ForNotable Features
WildFly (JBoss)Red Hat's reference implementation of Jakarta EE Full PlatformLarge enterprises, microservices with full EE spec supportWildFly Swarm/JAR overlay for uber-jar distribution, Kubernetes operators, extensive management CLI
Payara ServerGlassFish-based server with commercial support optionsMigration from Oracle/Sun GlassFish, high-availability clustersAuto-deployment of MicroProfile, built-in clustering (Hazelcast), Payara Micro for lightweight deployment
OpenLiberty (IBM)Modular, cloud-native Jakarta EE serverContainerized deployments, fast startup in KubernetesFeature-based activation (only load what you need), rapid restart, OpenTelemetry built-in, Liberty Profile
TomEEApache Tomcat + EJB/CDI/JAX-RS addonsLightweight deployments, teams already using TomcatSmallest footprint of full EE servers, pluggable profiles (Plus, PlusJSR356, JavaEE), Maven plugin for assembly
QuarkusCloud-native Java framework with Jakarta EE compatibilityNative compilation with GraalVM, Kubernetes-first architectureSuper-fast startup (< 1s), dev mode with hot reload, extension-based architecture, not a full container but supports most specs

Integration Approaches

MicroProfile Integration

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>

Database Connectivity

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);
    }
}

JMS Messaging

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);
    }
}

Kubernetes Deployment Manifest

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

Complete Working Example

JAX-RS Resource with CDI, JPA, and Bean Validation

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) {}
}

CDI Service Layer with JPA Persistence

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) {}
}

JPA Entity with Bean Validation

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; }
}

Common Pitfalls

PitfallCausePrevention
javax vs jakarta namespace confusionMixing old javax.servlet imports with new server or vice versaUse jakarta.* exclusively; verify all transitive dependencies declare Jakarta namespaces
CDI proxy limitations@RequestScoped proxies cannot be used in static contexts or when serializingNever call .getClass() on a CDI bean expecting the target type — use interfaces and inject at runtime
Transaction boundary misconfigurationPlacing @Transactional on repository methods instead of service layer causes nested transaction overheadAnnotate service/manager methods only; repositories remain non-transactional and rely on the caller's context
JSF state management bloatServer-side view state stored in HTTP session grows large for pages with many componentsUse 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.xmlCDI scanning fails if beans.xml is absent in a module that requires explicit bean discoveryInclude an empty <beans/> descriptor in WEB-INF/beans.xml when using bean-discovery-mode="all"
Memory pressure on large WARsHeavy JSF pages with many components, unlimited session scopes holding entitiesProfile heap usage with Eclipse MAT; implement lazy loading (FetchType.LAZY) for all entity associations

Constraints

MUST DO

  • Use jakarta.* namespace exclusively — never import javax.* packages in new code
  • Set JTA transaction boundaries at the service layer, not on individual DAO or repository methods
  • Include an explicit beans.xml with bean-discovery-mode="annotated" or "all" for CDI modules
  • Configure connection pooling via the container (not application-level pools like HikariCP directly)
  • Use Jakarta Bean Validation annotations (@NotNull, @Size) on DTOs passed to JAX-RS resources
  • Separate entity models from DTOs at service boundaries — never expose JPA entities to the REST layer

MUST NOT DO

  • Mix javax.* and jakarta.* dependencies in the same application — this causes class loader conflicts
  • Use @Stateless or @Singleton EJB annotations for everything — prefer CDI @ApplicationScoped for stateless services
  • Store large objects (blobs, lists of entities) in HTTP sessions without size limits
  • Annotate entity fields with Bean Validation instead of using XML mapping or separate constraint groups
  • Deploy applications as exploded WARs in production — use packaged .war archives for performance and security

Output Template

When configuring or reviewing a Jakarta EE application, produce:

  1. Container Selection — Recommended server with rationale (full platform vs. web profile, cloud-native requirements)
  2. Build Configuration — Complete pom.xml dependency declarations with scope annotations
  3. Component Architecture — WAR structure diagram listing all layers and their dependencies
  4. CDI Wiring Plan — Bean scopes, producer methods, and interceptors configured for cross-cutting concerns
  5. Transaction Design — Service method transaction attributes and boundary decisions

Related Skills

SkillPurpose
microprofileCloud-native overlay specifications (config, health, metrics) that run on top of Jakarta EE
jakarta-migrationStep-by-step process for migrating Java EE 8 (javax.*) applications to Jakarta EE 9+

Live References

Authoritative documentation links for the Jakarta EE platform.

저장한 Skills