Jakarta EE — Explained with Examples
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[<b>Client Tier</b>]
Browser[Browser / Mobile]
Other[Other Services]
end
subgraph Web[<b>Web Tier</b>]
Servlet[Servlets<br/>HTTP handling]
JAXRS[JAX-RS<br/>REST endpoints]
JSP[JSP / Facelets<br/>Views]
end
subgraph Business[<b>Business Tier</b>]
EJB[EJB<br/>Business logic]
CDI[CDI<br/>Dependency Injection]
BeanValidation[Bean Validation]
end
subgraph Data[<b>Data Tier</b>]
JPA[JPA<br/>Object-relational mapping]
JTA[JTA<br/>Transactions]
JMS[JMS<br/>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:deployCommon 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