Jakarta EE — Explained with Examples
In this tutorial, you'll learn about Jakarta EE. We cover key concepts, practical examples, and best practices to help you understand and apply this topic effectively.
Jakarta EE (formerly Java EE) is a set of enterprise Java specifications for building large-scale, distributed, and transactional applications, providing standardized APIs for Web Services, persistence, messaging, and Dependency Injection.
Why Jakarta EE Matters
Jakarta EE is the open-source evolution of Java EE, governed by the Eclipse Foundation. It powers critical enterprise systems in banking, insurance, telecom, and government. Unlike Spring (a framework), Jakarta EE is a standard — your skills transfer across any compliant implementation (Payara, WildFly, TomEE, GlassFish). DodaTech uses Jakarta EE for legacy system integration and batch processing pipelines.
Jakarta EE vs Spring Boot
| Feature | Jakarta EE | Spring Boot |
|---|---|---|
| Philosophy | Standardized platform | Framework opinionated |
| Server | Requires app server (Payara, WildFly) | Embedded server (Tomcat, Jetty) |
| DI | CDI (Contexts and Dependency Injection) | Spring IoC |
| Persistence | JPA (Hibernate) | Spring Data JPA |
| REST | JAX-RS (Jersey, RESTEasy) | Spring MVC |
| Startup time | Slower (app server) | Faster (embedded) |
| Weight | Heavier (full app server) | Lighter (only needed starters) |
Jakarta EE Architecture
graph TD
subgraph Client[Client Tier]
Browser[Browser / Mobile]
Other[Other Services]
end
subgraph Web[Web Tier]
Servlet[Servlets
HTTP handling]
JAXRS[JAX-RS
REST endpoints]
JSP[JSP / Facelets
Views]
end
subgraph Business[Business Tier]
EJB[EJB
Business logic]
CDI[CDI
Dependency Injection]
BeanValidation[Bean Validation]
end
subgraph Data[Data Tier]
JPA[JPA
Object-relational mapping]
JTA[JTA
Transactions]
JMS[JMS
Messaging]
end
Client --> Servlet
Client --> JAXRS
Servlet --> EJB
JAXRS --> CDI
EJB --> JPA
EJB --> JTA
EJB --> JMS
style Jakarta fill:#f59e0b,color:#fff
Servlet — The Foundation
Servlets handle HTTP requests and responses. They are the lowest-level web API in Jakarta EE.
import jakarta.servlet.*;
import jakarta.servlet.http.*;
import jakarta.servlet.annotation.*;
import java.io.*;
@WebServlet("/api/hello")
public class HelloServlet extends HttpServlet {
@Override
public void init() throws ServletException {
System.out.println("HelloServlet initialized");
}
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp)
throws IOException {
resp.setContentType("application/json");
resp.setCharacterEncoding("UTF-8");
String name = req.getParameter("name");
String greeting = "Hello, " + (name != null ? name : "World") + "!";
try (PrintWriter out = resp.getWriter()) {
out.write("{\"message\": \"" + greeting + "\"}");
}
}
@Override
public void destroy() {
System.out.println("HelloServlet destroyed");
}
}
Expected output when hitting GET /api/hello?name=Jakarta:
{"message": "Hello, Jakarta!"}
JAX-RS — Building REST APIs
JAX-RS (JSR 370) provides annotations for RESTful Web Services. It's the Jakarta EE equivalent of Spring MVC.
import jakarta.ws.rs.*;
import jakarta.ws.rs.core.*;
import jakarta.inject.Inject;
import java.util.*;
@Path("/api/v1/devices")
@Produces(MediaType.APPLICATION_JSON)
@Consumes(MediaType.APPLICATION_JSON)
public class DeviceResource {
@Inject
private DeviceService deviceService;
@GET
public Response getAllDevices(
@QueryParam("page") @DefaultValue("0") int page,
@QueryParam("size") @DefaultValue("20") int size) {
List<DeviceDTO> devices = deviceService.findAll(page, size);
return Response.ok(devices).build();
}
@GET
@Path("/{id}")
public Response getDevice(@PathParam("id") Long id) {
return deviceService.findById(id)
.map(dto -> Response.ok(dto).build())
.orElse(Response.status(Response.Status.NOT_FOUND).build());
}
@POST
public Response createDevice(DeviceDTO device) {
DeviceDTO created = deviceService.create(device);
return Response.status(Response.Status.CREATED)
.entity(created)
.build();
}
@PUT
@Path("/{id}")
public Response updateDevice(@PathParam("id") Long id, DeviceDTO device) {
DeviceDTO updated = deviceService.update(id, device);
return Response.ok(updated).build();
}
@DELETE
@Path("/{id}")
public Response deleteDevice(@PathParam("id") Long id) {
deviceService.delete(id);
return Response.noContent().build();
}
}
Deployment note: This resource must be registered in an Application subclass:
import jakarta.ws.rs.ApplicationPath;
import jakarta.ws.rs.core.Application;
@ApplicationPath("/api")
public class RestApplication extends Application {
// JAX-RS automatically discovers @Path resources
}
JPA — Object-Relational Mapping
Jakarta Persistence (JPA) maps Java objects to database tables. It's the same API that Spring Data JPA builds upon.
import jakarta.persistence.*;
import jakarta.validation.constraints.*;
import java.time.LocalDateTime;
@Entity
@Table(name = "devices")
@NamedQuery(name = "Device.findByType",
query = "SELECT d FROM Device d WHERE d.type = :type")
public class Device {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Column(nullable = false, unique = true)
@NotBlank
private String name;
@Column(nullable = false)
private String type;
@Column(name = "signal_strength")
private Integer signalStrength;
@Column(nullable = false)
private Boolean connected = false;
@Version
private Integer version;
@Column(name = "created_at", updatable = false)
private LocalDateTime createdAt;
@PrePersist
protected void onCreate() {
createdAt = LocalDateTime.now();
}
// Getters and setters
}
import jakarta.ejb.Stateless;
import jakarta.persistence.*;
import java.util.*;
@Stateless
public class DeviceRepository {
@PersistenceContext(unitName = "dodatechPU")
private EntityManager em;
public List<Device> findAll(int page, int size) {
return em.createNamedQuery("Device.findAll", Device.class)
.setFirstResult(page * size)
.setMaxResults(size)
.getResultList();
}
public Optional<Device> findById(Long id) {
return Optional.ofNullable(em.find(Device.class, id));
}
public Device save(Device device) {
if (device.getId() == null) {
em.persist(device);
return device;
}
return em.merge(device);
}
public void delete(Long id) {
findById(id).ifPresent(em::remove);
}
public List<Device> findByType(String type) {
return em.createNamedQuery("Device.findByType", Device.class)
.setParameter("type", type)
.getResultList();
}
}
CDI — Contexts and Dependency Injection
CDI is the Jakarta EE Dependency Injection standard. It's equivalent to Spring's IoC container.
import jakarta.enterprise.context.ApplicationScoped;
import jakarta.inject.Inject;
import jakarta.transaction.Transactional;
import java.util.*;
import java.util.stream.*;
@ApplicationScoped
public class DeviceService {
@Inject
private DeviceRepository repository;
@Inject
private DeviceMapper mapper;
public List<DeviceDTO> findAll(int page, int size) {
return repository.findAll(page, size).stream()
.map(mapper::toDTO)
.collect(Collectors.toList());
}
public Optional<DeviceDTO> findById(Long id) {
return repository.findById(id).map(mapper::toDTO);
}
@Transactional
public DeviceDTO create(DeviceDTO dto) {
Device device = mapper.toEntity(dto);
return mapper.toDTO(repository.save(device));
}
@Transactional
public DeviceDTO update(Long id, DeviceDTO dto) {
Device existing = repository.findById(id)
.orElseThrow(() -> new IllegalArgumentException("Device not found: " + id));
existing.setName(dto.name());
existing.setType(dto.type());
existing.setSignalStrength(dto.signalStrength());
existing.setConnected(dto.connected());
return mapper.toDTO(repository.save(existing));
}
@Transactional
public void delete(Long id) {
repository.delete(id);
}
}
EJB — Enterprise JavaBeans
EJBs handle transaction management, security, and concurrency declaratively. Modern Jakarta EE uses EJB Lite (annotations on POJOs).
import jakarta.ejb.*;
import java.util.concurrent.*;
@Singleton
@ConcurrencyManagement(ConcurrencyManagementType.CONTAINER)
public class DeviceCounter {
private final AtomicLong totalDevices = new AtomicLong(0);
@Lock(LockType.READ)
public long getTotalDevices() {
return totalDevices.get();
}
@Lock(LockType.WRITE)
public void increment() {
totalDevices.incrementAndGet();
}
@Schedule(hour = "0", minute = "0", second = "0")
public void resetDaily() {
totalDevices.set(0);
System.out.println("Device counter reset for new day");
}
}
Deployment — WildFly Example
<?xml version="1.0" encoding="UTF-8"?>
<persistence version="3.0"
xmlns="https://jakarta.ee/xml/ns/persistence">
<persistence-unit name="dodatechPU" transaction-type="JTA">
<jta-data-source>java:/DodaTechDS</jta-data-source>
<properties>
<property name="jakarta.persistence.schema-generation.database.action"
value="drop-and-create"/>
<property name="hibernate.dialect"
value="org.hibernate.dialect.PostgreSQLDialect"/>
<property name="hibernate.show_sql" value="true"/>
</properties>
</persistence-unit>
</persistence>
# Deploy to WildFly
./standalone.sh -c standalone-full.xml
cp target/dodatech-api.war /path/to/wildfly/standalone/deployments/
# Or use the Maven plugin
mvn wildfly:deploy
Common Mistakes
- Confusing Jakarta EE with Spring APIs: Jakarta EE uses
jakarta.*packages (notjavax.*since Jakarta EE 9). Don't mix them. - Not configuring
persistence.xmlcorrectly: Missingpersistence-unitor wrongtransaction-typecauses deployment failures. - Forgetting to annotate
@Statelessor@Singleton: EJBs need explicit annotations. A POJO without them gets no transaction or security management. - Direct
EntityManagerinjection in JAX-RS resources: Always inject via a service layer. Controllers shouldn't know about persistence. - Using
EntityManageroutside transactions: CRUD operations onEntityManagerrequire an active transaction. Missing@TransactionalcausesTransactionRequiredException.
Practice Questions
- What is the difference between Jakarta EE and Java EE?
- How does CDI compare to Spring's Dependency Injection?
- What annotations does JAX-RS use for HTTP methods?
- What is the purpose of
@Versionin JPA? - How do EJBs handle concurrency?
Answers:
- Jakarta EE is the renamed, open-source evolution of Java EE after Oracle transferred it to the Eclipse Foundation. The package names changed from
javax.*tojakarta.*. - Both use annotations and interfaces for DI. CDI uses
@Injectinstead of Spring's@Autowired. CDI is the standard; Spring's DI is implementation-specific. @GET,@POST,@PUT,@DELETE,@PATCH. They map Java methods to HTTP methods on specific resource paths.@Versionprovides optimistic locking. Before updating, JPA checks that the version in the database matches the version read. If not, it throwsOptimisticLockException.@SingletonEJBs default to container-managed concurrency. Add@Lock(LockType.READ)for read methods (allow concurrent access) and@Lock(LockType.WRITE)for write methods (exclusive access).
Mini Project: Jakarta EE REST API
Build a complete REST API for a device management system:
- Create a JPA
@Entityfor devices with fields: id, name, type, signal strength, connected, timestamps - Implement a
@StatelessEJB repository with CRUD operations - Create a
@Path("/devices")JAX-RS resource with full CRUD - Add Bean Validation annotations to the entity
- Configure
persistence.xmlfor PostgreSQL - Deploy to WildFly or Payara
This mirrors how enterprise systems at DodaTech's partners handle device inventory management in manufacturing environments.
Built by the developers of DodaTech
Doda Browser, DodaZIP & Durga Antivirus Pro