# Jakarta EE From Zero

> Learn the enterprise Java standard that runs a huge share of big-company backends: what Jakarta EE actually is (specs vs app servers), CDI dependency injection, JAX-RS REST APIs, Jakarta Persistence, JTA transactions, validation and JSON binding, enterprise beans and messaging, security, and the cloud-native MicroProfile direction.


---

# Jakarta EE From Zero

Jakarta EE (the platform formerly called Java EE) is the *standard* for enterprise Java - a family of
specifications that a huge share of banks, insurers, governments, and large enterprises build on. If
[Spring Boot](/guides/spring-boot-from-zero) is the popular framework, Jakarta EE is the official
standard it grew up alongside: instead of one vendor's framework, it's a set of agreed-upon APIs
(dependency injection, REST, persistence, transactions, security) that *multiple* server vendors
implement. Learn it and a whole category of enterprise jobs opens up - and you'll understand where many
of Spring's ideas came from.

The mental model that makes Jakarta EE click is **"specification, not implementation."** You write code
against standard annotations (`@Inject`, `@Path`, `@Entity`), and an **application server** (WildFly,
Payara, Open Liberty…) provides the actual engine. Swap the server, keep your code. This guide builds
that model first, then walks the specs you'll actually use, demystifying each instead of handing you
boilerplate to copy.

> 📝 This assumes you know **Java** (classes, interfaces, generics, annotations). If that's shaky, do
> [Java From Zero](/guides/java-from-zero) first. New to frameworks generally?
> [What a Framework Even Is](/guides/what-a-framework-even-is) sets the stage, and the persistence phase
> builds directly on [Hibernate & JPA](/guides/hibernate-and-jpa-from-zero).

## How to read this

Read in order - it builds one example (a small `Product` REST service) spec by spec. Phases carry
difficulty badges so you can see the climb.

## The phases

**Part 1 - The platform (🟢 Basic)**
1. **[What Jakarta EE Is](01-what-jakarta-ee-is.md)** 🟢 - specs vs implementations, the Java EE → Jakarta rename, and how it compares to Spring.
2. **[The Application Server & Deployment](02-the-app-server-and-deployment.md)** 🟢 - WildFly/Payara/Open Liberty, WAR packaging, and the container that runs your code.

**Part 2 - The core specs (🟡 Intermediate)**
3. **[CDI: Contexts & Dependency Injection](03-cdi-dependency-injection.md)** 🟡 - the standard DI: `@Inject`, beans, scopes, qualifiers, producers.
4. **[JAX-RS: Building REST APIs](04-jax-rs-rest-apis.md)** 🟡 - `@Path`/`@GET`/`@POST`, JSON-B, and a real REST resource.
5. **[Jakarta Persistence (JPA)](05-jakarta-persistence.md)** 🟡 - container-managed `EntityManager`, persistence units, and how Hibernate fits in.
6. **[Transactions with JTA](06-transactions-with-jta.md)** 🟡 - container-managed transactions, `@Transactional`, and what JTA adds.
7. **[Validation & JSON Binding](07-validation-and-json-binding.md)** 🟡 - Jakarta Validation (`@NotNull`…) and JSON-B, wired into JAX-RS.
8. **[Enterprise Beans & Messaging](08-enterprise-beans-and-messaging.md)** 🟡 - `@Stateless` beans, scheduling, and asynchronous messaging.

**Part 3 - Production & beyond (🔴 Advanced → 🟢)**
9. **[Jakarta Security](09-jakarta-security.md)** 🔴 - authentication mechanisms, `@RolesAllowed`, and identity stores.
10. **[MicroProfile & Where to Go Next](10-microprofile-and-where-next.md)** 🟢 - cloud-native EE, how Quarkus/Helidon build on these specs, and what to build.

> Jakarta EE and Spring aren't enemies - they share DNA (Spring helped inspire CDI; both use JPA). Knowing
> the *standard* makes every Java framework, including Spring, easier to read.


---

# What Jakarta EE Is

If you've spent any time around enterprise Java, you've seen the name everywhere - on job postings, in old Stack Overflow answers, stamped across the docs for servers with names like WildFly, Payara, and Open Liberty. And if you look straight at it, "Jakarta EE" has always been a bit of a fog. Is it a framework? A library? A version of Java? A competitor to Spring? People throw the term around as if it's one thing, when it's really a category of things.

Here's the good news: there's one clean idea underneath all of it, and once it clicks, the whole landscape snaps into focus. Before any code, let's build the mental model - because *this* is the part that confuses every beginner, and *this* is the part that makes everything else make sense.

## The core idea: a specification, not an implementation

Most tools you've used are a single piece of software you download and run. Jakarta EE is not that. Jakarta EE is a **specification** - a written agreement about how a set of enterprise features *should behave* - and other people write the actual code that fulfills it.

📝 **Specification (spec)** - a precise, written contract defining a set of APIs: the annotations, interfaces, and behavior that compliant software must provide. The spec says *what* must exist and how it must act. It contains no working engine of its own.

📝 **Implementation** - an actual, runnable piece of software that provides everything the spec demands. A vendor reads the spec and builds the engine. Multiple vendors can implement the same spec, and they all interoperate because they obey the same contract.

So there are two halves, written by two different parties:

- **You** write code against the *standard* - you sprinkle in annotations like `@Inject`, `@Path`, and `@Entity`, defined by the spec.
- A **vendor's server** provides the *engine* that gives those annotations meaning at runtime - it does the injecting, the URL routing, the database mapping.

This is the framework bargain from [/guides/frameworks](_guide.md) taken one step further: not only does the platform call *your* code (inversion of control), but the platform itself is interchangeable. You code to the contract; you pick the engine later.

```java
package com.example.store;

import jakarta.ws.rs.GET;
import jakarta.ws.rs.Path;

@Path("/hello")
public class HelloResource {

    @GET
    public String sayHello() {
        return "Hello from a Jakarta EE server";
    }
}
```

*What just happened:* You wrote almost nothing - a class, a method, two annotations. `@Path("/hello")` and `@GET` are pure *specification*: they're from the Jakarta RESTful Web Services (JAX-RS) spec, and on their own they don't run anything. There's no `main()`, no server-start code, no URL-parsing loop. When you hand this class to *any* compliant Jakarta EE server, that server reads the annotations and wires up an HTTP endpoint at `/hello` that calls `sayHello()` on every GET request. The same file runs unchanged on WildFly, Payara, or Open Liberty - because all three implement the same contract.

```mermaid
flowchart LR
  A[Your code:<br/>standard annotations] --> B{Any compliant<br/>Jakarta EE server}
  B --> C[WildFly]
  B --> D[Payara]
  B --> E[Open Liberty]
```

💡 **Why this matters.** Because the contract is standardized and the engine is swappable, you're not locked into one vendor. A big enterprise can switch from one application server to another - for cost, support, or performance reasons - without rewriting its application. That portability is the entire reason this model exists, and it's deeply valued in places that plan in decades, not sprints.

## The platform is a bundle of specs

Here's the second thing to internalize: Jakarta EE is not *one* specification. It's an umbrella over *many* specs, each covering one concern of building a server-side enterprise application. When someone says "a Jakarta EE server," they mean software that implements the whole bundle.

📝 **Jakarta EE platform** - a curated collection of individual specifications, versioned and released together, that together cover the common needs of enterprise applications: dependency injection, web APIs, database access, transactions, validation, security, and more.

The specs this guide will walk through, and the one job each one does:

- **CDI** (Contexts and Dependency Injection) - wiring objects together; the `@Inject` mechanism.
- **JAX-RS** (Jakarta RESTful Web Services) - building REST APIs; `@Path`, `@GET`, and friends.
- **Jakarta Persistence (JPA)** - mapping Java objects to database tables; `@Entity`.
- **JTA** (Jakarta Transactions) - making a group of database operations succeed or fail as one unit.
- **Bean Validation** - declaring rules on data (`@NotNull`, `@Email`) and enforcing them.
- **Jakarta Security** - authentication and authorization, the standard way.
- **EJB** (Enterprise Beans) and **Jakarta Messaging** - older heavyweight components and asynchronous message queues.

💡 **The mental model:** Jakarta EE is to enterprise Java what a well-stocked toolbox is to a workshop - not a single tool, but a coordinated set, each designed to fit the others. You don't have to use all of them. A small service might touch only CDI, JAX-RS, and JPA. But they're guaranteed to work together because they were specified and tested as a family.

## A short history you actually need: Java EE → Jakarta EE

You *will* run into this within your first hour of reading older material, so let's get ahead of it. The name changed, and so did something in the code that trips up everyone.

For roughly two decades, this platform was called **Java EE** (Java Platform, Enterprise Edition), stewarded by Sun and then Oracle. In 2017, Oracle handed the project to the **Eclipse Foundation**, a vendor-neutral open-source body. For trademark reasons Eclipse couldn't keep the word "Java" in the name, so the platform was renamed **Jakarta EE**, and its evolution moved to an open community process.

That rename forced a change that matters in your code: **the package namespace switched from `javax.*` to `jakarta.*`.**

```java
// PRE-Jakarta-9 (old Java EE) - you'll see this in old tutorials:
import javax.persistence.Entity;
import javax.ws.rs.GET;

// Jakarta EE 9 and later - the modern namespace:
import jakarta.persistence.Entity;
import jakarta.ws.rs.GET;
```

*What just happened:* The *exact same* annotations - `@Entity`, `@GET` - moved from packages starting with `javax` to packages starting with `jakarta`. The functionality is identical; only the import path changed. This happened in **Jakarta EE 9**, the deliberate "big rename" release. So an `import javax.persistence...` line is a fingerprint: it tells you the code (or the tutorial) predates this transition.

⚠️ **This is the single most common beginner trap in modern Jakarta EE.** You copy a code sample from a 2018 blog post, paste it into a current project, and nothing compiles - because the sample imports `javax.*` while your dependencies provide `jakarta.*`. When that happens, it's almost never a mysterious bug. It's the namespace. Mentally translate `javax.` → `jakarta.` and the imports resolve. (`javax.*` still exists for a few things that belong to core Java SE rather than the enterprise platform, but for the enterprise specs in this guide, modern code is `jakarta.*`.)

## Jakarta EE vs Spring - a clear-eyed comparison

This is the question on everyone's mind, so let's answer it straight, without picking a winner.

[Spring](/guides/spring-boot-from-zero) and Jakarta EE solve overlapping problems - dependency injection, REST endpoints, data access, transactions - but they come from different philosophies:

- **Spring** is *one opinionated framework*, built and governed by one organization (originally Pivotal, now Broadcom/VMware). It's a single product line you adopt as a whole. It's enormously popular, especially in startups and modern web shops, and Spring Boot made it famously fast to get going.
- **Jakarta EE** is a *vendor-neutral standard* implemented by many competing servers. You adopt the spec, then choose your engine. It's extremely common in large, long-lived enterprises - banks, insurers, governments, telecoms - where vendor independence and stability are prized.

They also share a surprising amount of DNA. Spring's early dependency-injection ideas directly influenced the design of CDI, the standard DI spec. And both worlds lean on the *same* underlying database library most of the time: **Hibernate** is the dominant implementation of Jakarta Persistence (JPA), and Spring Data sits on top of JPA too. So the moment you learn `@Entity` here, you've learned something Spring developers use daily.

💡 **Neither is "better" - they're different worlds, and both are deeply employable.** Choosing between them is mostly about which ecosystem your team and your industry live in, not about one being technically superior. Knowing both makes you more valuable, not less.

## Why learn it

If you're coming from Spring or from plain Java, it's fair to ask: why spend time here at all?

Two concrete reasons:

1. **A whole category of jobs runs on this.** Application servers and Jakarta EE underpin an enormous amount of the world's enterprise software - the unglamorous, mission-critical systems that move money and run institutions. That work is stable, well-paid, and not going anywhere. Reading and writing Jakarta EE is a marketable skill in its own right.
2. **It makes Spring easier to read, not harder.** Because the two share concepts and even libraries, learning the *standard* version of dependency injection, persistence, and validation gives you the vocabulary to understand what Spring is doing under its own conventions. You start seeing the shared bones beneath both frameworks.

And it sets up the very next thing you need to understand. We've talked a lot about "a server" or "the engine" that brings your annotations to life. In the next phase we look that engine in the eye: what an **application server** actually is, how your code gets *deployed* into it, and why this deploy-into-a-running-server model is so different from the standalone apps you may be used to.

## Recap

1. **Jakarta EE is a specification, not an implementation.** The spec defines standard APIs (annotations and interfaces like `@Inject`, `@Path`, `@Entity`); separate *vendors* write the servers that actually provide the engine.
2. **You code to the standard; the server provides the runtime.** The same application runs unchanged on any compliant server (WildFly, Payara, Open Liberty), which is what gives enterprises vendor portability.
3. **The platform is a bundle of specs**, not one library - CDI (DI), JAX-RS (REST), JPA (persistence), JTA (transactions), Bean Validation, Security, plus EJB and Messaging - coordinated to work together.
4. **It was "Java EE" under Oracle, became "Jakarta EE" at the Eclipse Foundation**, and the package namespace changed `javax.*` → `jakarta.*` in Jakarta EE 9. Old `javax.` imports = pre-Jakarta-9 code, and they're the #1 reason old samples won't compile.
5. **Jakarta EE vs Spring is a tie, not a contest:** Spring is one opinionated framework; Jakarta EE is a vendor-neutral standard. They share DNA (CDI was Spring-influenced; both use JPA/Hibernate). Different worlds, both employable.
6. **Learning the standard pays twice:** it opens a whole category of enterprise jobs, and it makes Spring's concepts easier to read because you understand the shared foundations.

## Quick check

Lock in the one idea that everything else builds on:

```quiz
[
  {
    "q": "What does it mean that Jakarta EE is a 'specification, not an implementation'?",
    "choices": [
      "It defines standard APIs (annotations and interfaces) that separate vendors implement in their servers",
      "It is a single program you download and run directly",
      "It is a specific version of the Java language",
      "It is Oracle's commercial replacement for the JVM"
    ],
    "answer": 0,
    "explain": "Jakarta EE is a written contract of standard APIs. Vendors like Red Hat (WildFly) and Payara build the actual servers that fulfill it, so the same code runs on any compliant implementation."
  },
  {
    "q": "You copy an old tutorial and it imports `javax.persistence.Entity`, but your modern project won't compile. What's the most likely cause?",
    "choices": [
      "The namespace changed from `javax.*` to `jakarta.*` in Jakarta EE 9, so modern code needs `jakarta.persistence.Entity`",
      "The `@Entity` annotation was removed from Jakarta EE entirely",
      "You must downgrade your JVM to run any persistence code",
      "Spring and Jakarta EE cannot share the same database library"
    ],
    "answer": 0,
    "explain": "The Eclipse Foundation rename forced the package namespace from javax.* to jakarta.* in Jakarta EE 9. The annotation is identical; only the import path changed. Old javax.* imports are a fingerprint of pre-Jakarta-9 code."
  },
  {
    "q": "Which statement best captures the real difference between Jakarta EE and Spring?",
    "choices": [
      "Jakarta EE is a vendor-neutral standard with many implementations; Spring is one opinionated framework - both are widely used and share DNA like JPA/Hibernate",
      "Spring is a specification and Jakarta EE is its only implementation",
      "Jakarta EE is always faster and should always be chosen over Spring",
      "They share no concepts or libraries and cannot be learned together"
    ],
    "answer": 0,
    "explain": "Spring is a single opinionated framework; Jakarta EE is a vendor-neutral standard implemented by competing servers. They overlap heavily (CDI was Spring-influenced; both build on JPA/Hibernate). Neither is universally 'better.'"
  }
]
```


---

# The Application Server & Deployment

[Phase 1](01-what-jakarta-ee-is.md) called Jakarta EE a set of *specs* that some *server* implements. This phase is about that server - the thing that actually runs your code - and the single biggest mental shift between classic Jakarta EE and the framework world you might already know.

Here's the shift, stated plainly so it sticks. If you've touched Spring Boot, you're used to your application *being* the program: you build one jar, run `java -jar app.jar`, and an embedded web server boots up *inside* your process. Classic Jakarta EE flips that. Your application is **not** a standalone program. It's a *bundle of code* that you hand to a long-running server - and that server provides the engine: the web server, dependency injection, transactions, connection pooling, all of it. You write the parts that are specific to your app; the server brings everything else.

Hold that picture - *app deployed into a running server* - and the rest of this phase falls into place.

## The container model

📝 An **application server** is a long-running program that hosts your Jakarta EE application and provides implementations of the EE specs. You don't start your app; you start the *server*, then deploy your app *into* it. The server is already running, already has a thread pool, a web listener, a transaction manager, a CDI engine - and it lends all of that to whatever apps you drop in.

This is often called the **container** model. The server is a container; your app lives inside it. When a request arrives, the server receives it, finds the right piece of your code, hands it the services it needs (an injected bean, an open transaction), runs it, and sends the response back. Your code never touches the socket, never manages a thread, never opens the transaction by hand. That's the deal: you give up control of the plumbing, and in exchange you never write the plumbing.

```mermaid
flowchart LR
  WAR[Your app<br/>WAR file] -->|deployed into| AS[Application server<br/>WildFly / Payara]
  AS -->|provides| SPECS[CDI · JTA · JPA<br/>web server · pooling]
  REQ[HTTP request] --> AS
  AS --> RESP[HTTP response]
```

*What just happened:* The diagram shows the whole model on one line. Your WAR (we'll define that shortly) gets deployed *into* the running application server. The server supplies the specs - CDI, transactions, persistence, the web server itself - and sits between incoming requests and your code, wiring the two together. Your app is a tenant; the server is the building with the power and water already on.

📝 You'll meet these application servers by name. The common ones today: **WildFly** (Red Hat's, open source), **Payara** (a commercially-supported descendant of GlassFish), **Open Liberty** (IBM's, lightweight and modular), and **GlassFish** (the original reference implementation). They all implement the same specs - that's the whole point of a standard - so the same WAR can run on any of them. (This is the "swap the server, keep your code" promise from Phase 1, made concrete.)

If the idea of "a server you start, then deploy apps into" feels fuzzy, [What a Server Even Is](/guides/what-a-server-is) builds that picture from the ground up - worth a detour if servers are new to you.

## App server vs servlet container

You'll also hear about **Tomcat** and **Jetty**, and people sometimes lump them in with WildFly and Payara. They're not the same kind of thing, and the difference matters.

📝 A **servlet container** runs *web* applications - it speaks HTTP, manages the request/response cycle, and runs servlets (the low-level Java web component). Tomcat and Jetty are servlet containers. They're excellent at one job: serving web requests. But that's *all* they provide out of the box.

📝 A **full application server** is a servlet container *plus* the rest of the Jakarta EE specs: CDI (dependency injection), JTA (transactions), JPA (persistence), messaging, security, and more - all built in and ready to use. WildFly, Payara, Open Liberty, and GlassFish are full application servers.

So when do you need which? A rough rule:

- **Servlet container (Tomcat/Jetty)** - you're building a web app and you'll pull in whatever extra libraries you want yourself (this is, in fact, how Spring Boot works under the hood - it embeds Tomcat and adds its own everything-else). You want a thin, fast HTTP engine and full control over the rest.
- **Full application server (WildFly/Payara)** - you want the whole EE stack provided and managed *for* you: declare a transaction with an annotation and the server's transaction manager handles it; inject a bean and the server's CDI engine wires it. Classic enterprise Jakarta EE assumes this.

💡 The line between them has genuinely blurred (more on that at the end), but the conceptual split is real and worth keeping: *servlet container = HTTP only; application server = HTTP + all the EE specs.*

## Packaging: WAR (and EAR)

So you've written your app. How do you hand it to the server? You bundle it into a standard archive. The one you'll use almost always is a **WAR**.

📝 A **WAR** (Web ARchive) is a single file - a ZIP, really, like a [JAR](/guides/java-from-zero) - that bundles your compiled classes, your web resources (HTML, config), and a `WEB-INF/` folder of metadata. It's the unit you deploy: one `.war` file that the server knows how to unpack and run. A minimal Jakarta EE project headed for a WAR looks like this:

```text
my-shop/
├── pom.xml
└── src/main/
    ├── java/com/example/shop/
    │   └── ProductResource.java     ← your code
    └── webapp/
        └── WEB-INF/
            └── web.xml              ← (often optional now)
```

*What just happened:* This is the standard Maven layout from [Java's tooling phase](/guides/java-from-zero), with one addition: a `webapp/` directory holding web content and a `WEB-INF/` folder for web metadata. When you build, Maven packages all of this - compiled classes, resources, metadata - into one `my-shop.war`. The `WEB-INF/web.xml` deployment descriptor used to be mandatory; modern Jakarta EE leans on annotations instead, so it's frequently absent entirely.

The one line that makes Maven produce a WAR instead of a JAR is the packaging type in `pom.xml`:

```xml
<project>
    <groupId>com.example</groupId>
    <artifactId>my-shop</artifactId>
    <version>1.0.0</version>
    <packaging>war</packaging>   <!-- not "jar" -->
</project>
```

*What just happened:* `<packaging>war</packaging>` tells Maven to assemble a `.war` archive with the right internal structure (the `WEB-INF/classes`, `WEB-INF/lib` layout the server expects) rather than a plain JAR. That one word is the difference between an artifact a server can deploy and one it can't.

There's also a bigger archive, the **EAR**:

📝 An **EAR** (Enterprise ARchive) bundles *multiple* modules - several WARs, shared libraries, enterprise-bean modules - into one deployable unit, for large applications composed of many parts. You'll see EARs in big, older enterprise systems. For most work today a single WAR is all you need, so don't worry about EARs until you meet one.

⚠️ This is the exact opposite of the Spring Boot model, and the contrast is the clearest way to understand both. **Spring Boot** builds a *fat JAR* with an *embedded server inside it* - your artifact contains its own web server and runs standalone (`java -jar app.jar`). **Classic Jakarta EE** builds a *WAR with no server inside* - the server already exists, running, and you deploy *into* it. Boot flipped the industry's default from "deploy your app to a server" to "your app ships its own server." Knowing both models is most of what this phase is for. (If Boot is your background, [Spring Boot From Zero](/guides/spring-boot-from-zero) is the mirror image of this guide.)

## Deploying

Deploying a WAR is refreshingly anticlimactic. In the classic model there are two common ways:

1. **Drop it in the deploy folder.** Every server watches a directory (WildFly's is `standalone/deployments/`). Copy your WAR there and the running server notices the new file and deploys it - no restart.
2. **Use the admin console or CLI.** Servers ship a web admin UI and a command-line tool (WildFly has `jboss-cli`) for deploying, undeploying, and managing apps - handy for scripts and remote servers.

The folder-drop is the simplest to picture:

```bash
# WildFly is already running. Just copy the WAR into its watched folder:
cp target/my-shop.war $WILDFLY_HOME/standalone/deployments/
```

*What just happened:* You copied your built WAR into the server's deployment directory. You didn't start your application - the server was already up. It detected the new file, unpacked it, scanned it for annotations (`@Path`, `@Inject`, `@Entity`), wired up the specs your code uses, and started serving it. The server log confirms it:

```console
INFO  [org.jboss.as.server.deployment] Starting deployment of "my-shop.war"
INFO  [org.jboss.weld] Processing CDI deployment: my-shop.war
INFO  [org.jboss.resteasy] Deploying JAX-RS application, path: /api
INFO  [org.jboss.as.server] Deployed "my-shop.war" (runtime-name: "my-shop.war")
```

*What just happened:* Reading top to bottom, the server announces it found your WAR, set up CDI for it (Weld is WildFly's CDI engine), registered your REST endpoints under `/api` (RESTEasy is its JAX-RS engine), and finished. Each log line is the server providing one spec to your code - exactly the container model in action. Your app is now live, and you wrote none of that wiring.

## The modern shift

Everything above is the *classic* model, and it's still very much alive in enterprises. But it's no longer the only way - the line has blurred, and you should know both pictures.

💡 The modern direction is the **self-contained, single-artifact** approach - the same idea Spring Boot popularized, now native to the Jakarta EE world:

- **Payara Micro** and **Open Liberty** let you run your app as a single executable bundle, server included - closer to `java -jar` than "deploy to a big server."
- **Quarkus** and **Helidon** (which we'll meet in [Phase 10](10-microprofile-and-where-next.md)) take this further: build one self-contained artifact, even a native binary, that boots in milliseconds - Jakarta EE specs without a separate server to manage.
- **Embedded servers** generally have become common, so "start a heavyweight server and deploy a WAR into it" is now one option among several.

So the plain summary: *deploy a WAR into a long-running application server* is the **classic** model, and *run a single self-contained artifact* is the **modern** one. Both are real, both are in production today, and you'll meet both - which is exactly why we covered the container model first. Once you understand that a server *provides* services to your code, the modern variants are just "the same services, packaged differently." (For the deeper "what is a server, and what does 'embedded' even mean" picture, [What a Server Even Is](/guides/what-a-server-is) is the companion read.)

Whichever model you're in, the single most important service that server provides - the one the rest of the guide builds on - is **dependency injection**. That's CDI, and it's next.

## Recap

1. **The container model:** classic Jakarta EE deploys your app *into* a long-running **application server**; the server provides the engine (web server, CDI, transactions, pooling) and your app is a tenant inside it. You start the *server*, not your app.
2. **Application servers** to know: WildFly, Payara, Open Liberty, GlassFish. All implement the same specs, so one WAR runs on any of them.
3. **Servlet container vs application server:** Tomcat/Jetty run web apps (HTTP only); full app servers add the rest of the EE specs (CDI, JTA, JPA, messaging…). Choose by whether you want the EE stack provided for you.
4. **Packaging:** a **WAR** (Web ARchive) bundles your classes + web resources + `WEB-INF/` metadata into one deployable file (`<packaging>war</packaging>` in Maven). An **EAR** bundles multiple modules for large apps.
5. **The Spring Boot contrast:** Boot ships a *fat JAR with an embedded server* (runs standalone); classic Jakarta EE ships a *WAR with no server* (deploys into one). Boot flipped the default.
6. **Deploying:** drop the WAR in the server's deploy folder (or use the admin console/CLI); the running server detects, scans, wires, and serves it.
7. **The modern shift:** Payara Micro, Open Liberty, Quarkus, and Helidon let you run Jakarta EE as a single self-contained artifact (Boot-style). Classic = deploy a WAR; modern = run one artifact. You'll meet both.

## Quick check

One quick pass over the container model before we dive into CDI:

```quiz
[
  {
    "q": "In the classic Jakarta EE model, what is the relationship between your application and the application server?",
    "choices": [
      "Your app is deployed INTO a long-running server that provides the specs (web server, CDI, transactions); you start the server, not your app",
      "Your app embeds the server inside itself and runs standalone with java -jar",
      "Your app and the server are the same artifact, compiled together into one binary",
      "The server is a library your app imports and calls directly"
    ],
    "answer": 0,
    "explain": "Classic Jakarta EE inverts the Spring Boot model: the application server runs continuously and provides the engine (web server, CDI, JTA, pooling). You deploy your app - a WAR - into that already-running server, which supplies all the plumbing."
  },
  {
    "q": "What is the difference between a servlet container (like Tomcat) and a full application server (like WildFly)?",
    "choices": [
      "A servlet container handles HTTP/web requests only; a full application server adds the rest of the EE specs - CDI, JTA, JPA, messaging - built in",
      "A servlet container is faster because it's written in C, while application servers are Java",
      "A servlet container is for production and an application server is only for development",
      "There is no difference; the two terms are interchangeable"
    ],
    "answer": 0,
    "explain": "Tomcat and Jetty are servlet containers: they serve web requests and run servlets, and nothing more out of the box. Full application servers (WildFly, Payara, Open Liberty, GlassFish) are servlet containers plus implementations of the rest of the Jakarta EE specs."
  },
  {
    "q": "How does classic Jakarta EE packaging differ from Spring Boot's?",
    "choices": [
      "Jakarta EE builds a WAR with no server inside (deployed into an existing server); Spring Boot builds a fat JAR with an embedded server (runs standalone)",
      "Jakarta EE builds a fat JAR; Spring Boot builds a WAR",
      "Both build identical WAR files; only the run command differs",
      "Jakarta EE produces a native binary while Spring Boot produces bytecode"
    ],
    "answer": 0,
    "explain": "Classic Jakarta EE produces a WAR containing only your code - the server already exists and you deploy into it. Spring Boot produces a fat JAR that bundles an embedded web server inside, so it runs on its own with java -jar. Boot flipped the industry default."
  }
]
```


---

# CDI: Contexts & Dependency Injection

In Phase 2 you packaged a `Product` app, dropped it on an application server, and watched the server
take over the running of your code. This phase is about the mechanism the server uses to *build and wire*
your objects once it's running - the engine sitting underneath nearly every Jakarta feature you'll meet
from here on. It's called **CDI**, and once it clicks, the rest of Jakarta EE stops feeling like a pile of
unrelated annotations and starts feeling like one connected system.

Here's the mental model to carry the whole way through: in a normal program, your objects build the
other objects they need with `new`. In a CDI program, **a container builds your objects for you and hands
them the things they depend on.** You stop writing `new`, and you start *describing* what you need. That
single inversion - control over object creation moving out of your code and into the container - is the
whole game.

📝 If you've read [Spring's dependency injection](/guides/spring-boot-from-zero), you already know this
idea cold. Spring has its own container; CDI is the **standard spec** for the exact same thing - same
inversion of control, same "container owns your objects," but defined by Jakarta EE rather than one
framework. The vocabulary maps almost one-to-one, and we'll point out each parallel as we go. If you
haven't met Spring, no problem - we build it up from scratch here.

## What CDI actually is

📝 **CDI - Contexts and Dependency Injection** - the standard dependency-injection container built into
Jakarta EE. Every compliant application server (Phase 2's WildFly, Payara, Open Liberty, and friends)
ships a CDI implementation. You don't add it as a library or pick a vendor; it's part of the platform.

The "**DI**" half is dependency injection: the container creates your objects and supplies the things
they depend on. The "**C**" half - Contexts - is CDI's answer to a question Spring also answers with
scopes: *how long does each object live, and who shares it?* We'll get to that.

📝 **Bean** - an object the CDI container creates, wires up, and manages. That's the entire definition.
A bean isn't a special base class or some exotic type; it's just one of *your* ordinary classes that CDI
has taken ownership of. (Spring devs: identical meaning. Same word, same concept.) When someone says
"make this a bean," they mean "let the container manage it."

So the shift, just like in Spring, is: stop writing `new ProductRepository()`, and instead tell the
container "this is yours to manage" and "this service needs one of those." CDI does the connecting. Let's
see how you say it.

## `@Inject` and beans

Say we're building a small `Product` service. We have a `ProductRepository` that stores products, and a
`ProductService` that uses it. The naive version has the service build its own repository:

```java
public class ProductService {
    private final ProductRepository repository = new ProductRepository(); // builds its own dependency

    public Product findById(long id) {
        return repository.findById(id);
    }
}
```
*What just happened:* `ProductService` reached out and constructed its own `ProductRepository`. It works,
but it welds the two together - there's no seam to swap the repository for a fake in a test, and the
service is now responsible for *choosing* its dependency, not just using it. CDI's whole pitch is to take
that first job away.

The CDI version *declares* the dependency and lets the container fill it in. The annotation is `@Inject`,
and the preferred place to put it is the **constructor**:

```java
import jakarta.inject.Inject;
import jakarta.enterprise.context.ApplicationScoped;

@ApplicationScoped
public class ProductService {
    private final ProductRepository repository;

    @Inject
    public ProductService(ProductRepository repository) { // CDI passes one in
        this.repository = repository;
    }

    public Product findById(long id) {
        return repository.findById(id);
    }
}
```
*What just happened:* `ProductService` no longer knows or cares *which* `ProductRepository` it gets - it
asks for one in its constructor and marks that constructor with `@Inject`. When the container builds a
`ProductService`, it sees the parameter, finds a matching `ProductRepository` bean, and passes it in. The
`repository` field is `final`: once CDI sets it, it can't change. (Spring devs: this is `@Autowired` on a
constructor, standardized as `@Inject` from the `jakarta.inject` package.)

💡 CDI also supports **field injection** (`@Inject` straight on the field), and you'll see it constantly
in older Jakarta/Java EE code. Prefer the constructor anyway, for the same reasons as Spring: the
constructor is a clear, visible list of what the class needs, the field can be `final`, and you can test
with a plain `new ProductService(fakeRepo)` - no container required. The constructor *is* the testing seam.

### `beans.xml` and "CDI is on by default"

In older Java EE, CDI only scanned an archive if it contained a marker file called `beans.xml`. You'll
still find it in projects, sometimes empty, sometimes setting a discovery mode:

```xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="https://jakarta.ee/xml/ns/jakartaee"
       version="4.0" bean-discovery-mode="annotated">
</beans>
```
*What just happened:* this `beans.xml` (under `WEB-INF/` for a war, or `META-INF/` for a jar) tells CDI to
discover beans in this archive. `bean-discovery-mode="annotated"` means "manage any class that carries a
bean-defining annotation like `@ApplicationScoped`" - which is the modern default.

💡 In modern Jakarta EE you usually **don't need `beans.xml` at all**. CDI is on by default, and any class
with a scope annotation is automatically a managed bean. The file survives mostly for explicit
configuration (interceptor ordering, alternatives) or pure historical habit. If you have no special
configuration, leave it out.

## Scopes: the "Contexts" in CDI

Now the part Spring devs will recognize as `@Scope`, and the part that gives CDI its first letter. 📝 A
**scope** answers two questions: *how long does a bean live*, and *who shares the same instance*. CDI
manages a separate **context** for each scope - a place where it keeps the live instances for that
lifetime. The main ones:

| Scope | Annotation | One instance per… | Use it for |
|-------|------------|-------------------|------------|
| Application | `@ApplicationScoped` | whole application | shared, stateless services & repositories |
| Request | `@RequestScoped` | single HTTP request | per-request data, request context |
| Session | `@SessionScoped` | user HTTP session | per-user state (a cart, login info) |
| Dependent | `@Dependent` (default) | each injection point | small helpers with no shared state |

```java
import jakarta.enterprise.context.ApplicationScoped;

@ApplicationScoped
public class ProductRepository {
    private final Map<Long, Product> store = new ConcurrentHashMap<>();

    public Product findById(long id) { return store.get(id); }
    public void save(Product p)      { store.put(p.id(), p); }
}
```
*What just happened:* `@ApplicationScoped` tells CDI "create **one** `ProductRepository` for the entire
application and share it everywhere it's injected." That's exactly what you want for a stateless store:
one instance, lazily created on first use, living as long as the app. Note the `ConcurrentHashMap` - an
application-scoped bean is shared across all concurrent requests, so its state must be thread-safe.

⚠️ **Scope mismatches are a classic CDI bug.** Injecting a shorter-lived bean into a longer-lived one is
the trap: put a `@RequestScoped` bean inside an `@ApplicationScoped` one and the singleton would naively
capture a *single* request's instance and reuse it forever - for every request, every user. CDI actually
saves you here: when you inject a narrower scope, it hands over a **proxy** (a stand-in object) that, on
each call, routes to the *correct* instance for the current context. So it works - but only because of the
proxy. Trouble shows up when a bean has no usable scope at all, or when you store a reference and bypass
the proxy. Rule of thumb: let the container inject, never cache the injected reference yourself, and make
sure every bean has an explicit scope.

Here's the shape of it - the container keeps a context per scope and serves instances from the right one:

```mermaid
flowchart TD
  App["@ApplicationScoped<br/>ProductService (1 for app)"]
  Repo["@ApplicationScoped<br/>ProductRepository (1 for app)"]
  Req["@RequestScoped<br/>per HTTP request"]
  App -->|injected| Repo
  App -.->|proxy to current request| Req
```

## Qualifiers: picking which implementation

Here's a problem injection alone can't solve. Suppose `ProductRepository` is an *interface* with two
implementations - an in-memory one for dev and a database-backed one for production. When `ProductService`
asks for a `ProductRepository`, CDI finds **two** candidates and fails with an *ambiguous dependency*
error: it doesn't know which you mean.

📝 A **qualifier** is a custom annotation that disambiguates - it tags a bean and, at the injection point,
says "give me the one tagged like this." (Spring solves the same problem with `@Qualifier("name")`; CDI
makes it a real, type-safe annotation instead of a string.)

```java
import jakarta.inject.Qualifier;
import java.lang.annotation.*;

@Qualifier
@Retention(RetentionPolicy.RUNTIME)
@Target({ ElementType.TYPE, ElementType.PARAMETER, ElementType.FIELD })
public @interface InMemory {}
```
```java
@ApplicationScoped
@InMemory                                  // tag this implementation
public class InMemoryProductRepository implements ProductRepository { /* ... */ }

// ...and at the injection point, ask for that specific one:
@Inject
public ProductService(@InMemory ProductRepository repository) {
    this.repository = repository;
}
```
*What just happened:* `@InMemory` is a custom qualifier - note the `@Qualifier` meta-annotation that makes
it one. We tag `InMemoryProductRepository` with it, and we put it on the constructor parameter. Now CDI
has an unambiguous match: of the two `ProductRepository` beans, inject the one qualified `@InMemory`. Swap
to production by changing the qualifier at the one injection point - the service code is otherwise
untouched. Type-safe, refactor-friendly, no magic strings.

## Producers and interceptors (brief)

Two more pieces round out CDI. You'll reach for them less often, but knowing they exist saves you from
fighting the container.

**Producers** handle the case where the thing you want to inject *isn't your class* to annotate - a
configured object, a value from a config file, something built by a factory. A `@Produces` method turns
any object into something injectable:

```java
@ApplicationScoped
public class ConfigProducer {
    @Produces
    public ProductPricing defaultPricing() {
        return new ProductPricing(0.20); // a configured, non-bean object, now injectable
    }
}
```
*What just happened:* `ProductPricing` is a plain object CDI doesn't know how to build on its own. The
`@Produces` method is a recipe: "whenever someone injects a `ProductPricing`, call this and use what it
returns." Now `@Inject ProductPricing pricing` works anywhere. (Spring devs: this is a `@Bean` factory
method on a `@Configuration` class.)

**Interceptors** handle cross-cutting concerns - logging, timing, transactions - that you'd otherwise
copy-paste into every method. You define an interceptor bound to an annotation, then tag the methods you
want wrapped. The classic example is `@Transactional` (from Jakarta Transactions): putting it on a method
makes CDI wrap the call in a database transaction, with no transaction code in your business logic. The
machinery is the same kind you'd use to write your own `@Logged` or `@Timed` binding.

💡 The big-picture payoff: **CDI is the backbone every other Jakarta spec plugs into.** The JAX-RS REST
resources you'll build next phase are CDI beans. EJBs are CDI beans. Your persistence layer gets injected
via CDI. Learn this one container and you've learned the wiring model for the entire platform - which is
exactly why the spec authors made it standard. (Spring devs: think `@Component` + `@Autowired` + `@Scope`
+ `@Qualifier` + `@Bean`, all standardized under one umbrella you get for free with the server.)

## Recap

1. **What CDI is:** Jakarta EE's standard dependency-injection container, built into every compliant
   application server. Same inversion-of-control idea as Spring's container - the container builds your
   objects and supplies their dependencies - but defined by the spec, not one framework. A **bean** is
   just an ordinary class the container manages.
2. **`@Inject`:** declare a dependency as a constructor parameter marked `@Inject`, and CDI supplies the
   matching bean. Prefer constructor injection over field injection - explicit, `final`, testable with a
   plain `new`. Modern CDI is on by default; `beans.xml` is usually optional.
3. **Scopes (the Contexts):** `@ApplicationScoped` (one per app), `@RequestScoped` (one per HTTP request),
   `@SessionScoped` (one per user session), `@Dependent` (default, one per injection point). ⚠️ Mind scope
   mismatches; let the container inject and never cache the injected reference.
4. **Qualifiers:** when two beans satisfy the same type, a custom `@Qualifier` annotation picks which to
   inject - the type-safe standard answer to "which implementation?"
5. **Producers & interceptors:** `@Produces` methods make non-bean objects injectable; interceptor
   bindings (like `@Transactional`) wrap methods with cross-cutting behavior. CDI is the backbone every
   other Jakarta spec - JAX-RS, EJB, persistence - plugs into.

With CDI under your belt you have the wiring model for the whole platform. Next we put a REST layer on
front of these beans and start serving real HTTP with JAX-RS.

## Quick check

Test yourself on the ideas that have to stick from this phase:

```quiz
[
  {
    "q": "What is a 'bean' in CDI?",
    "choices": [
      "An ordinary class that the CDI container creates, wires up, and manages",
      "A special CDI-only base class your classes must extend",
      "An XML file that lists your dependencies",
      "Any class located inside a beans.xml archive and nothing else"
    ],
    "answer": 0,
    "explain": "A bean is just one of your normal classes that the container has taken ownership of - it creates the instance, injects its dependencies, and manages its lifecycle. There's no special base class or type involved."
  },
  {
    "q": "You annotate ProductRepository with @ApplicationScoped. How many instances does CDI create, and what's the consequence?",
    "choices": [
      "One per application, shared everywhere - so its mutable state must be thread-safe",
      "One per HTTP request, so it resets every request",
      "One per injection point, so each injector gets its own copy",
      "One per user session, isolated to each logged-in user"
    ],
    "answer": 0,
    "explain": "@ApplicationScoped means a single instance for the whole application, shared across all concurrent requests. Because it's shared, any mutable state inside it (like the product store) must be thread-safe."
  },
  {
    "q": "ProductRepository is an interface with two implementations. Injecting it fails with an 'ambiguous dependency' error. What's the standard CDI fix?",
    "choices": [
      "Define a custom @Qualifier annotation, tag the chosen implementation with it, and add it at the injection point",
      "Delete one of the implementations so only one remains",
      "Switch from constructor injection to field injection",
      "Add a beans.xml file, which automatically resolves the ambiguity"
    ],
    "answer": 0,
    "explain": "When multiple beans satisfy the same type, CDI can't choose. A custom @Qualifier annotation tags a specific implementation and is named at the injection point, giving the container an unambiguous, type-safe match."
  }
]
```


---

# JAX-RS: Building REST APIs

In [Phase 3](03-cdi-dependency-injection.md) you got the container to build and wire your objects: a
`ProductService` that holds the actual logic, handed to whatever needs it via `@Inject` - but so far
only reachable from other Java code. This phase opens a door to it from the outside world: an HTTP
request from a browser, a mobile app, or another service can now reach in and ask for a product.

The mental model to hold onto: **a JAX-RS resource is the translator between HTTP and your method calls.**
A request arrives as raw bytes - a URL, a method like `GET`, maybe a JSON body. Something has to read
that, figure out what's being asked, call the right Java method, and turn the answer back into an HTTP
response. That "something" is a *resource class*. You don't write the part that parses HTTP or formats the
reply - the application server does that. You write small methods and *label* them so the server knows
"when `GET /api/products` comes in, call this one." Same inversion of control you saw with CDI: you don't
call the framework, the framework calls you.

📝 **JAX-RS (Jakarta RESTful Web Services) is the standard REST spec** - the official, vendor-neutral way
to build HTTP APIs in Jakarta EE. If you've seen Spring's `@RestController`
([Spring Boot From Zero](/guides/spring-boot-from-zero)), JAX-RS is the standards-body equivalent: same
job, different annotations, and your app server (WildFly, Payara, Open Liberty) ships the engine instead
of one vendor's framework. If words like *HTTP method*, *status code*, or *resource* feel fuzzy, the
[REST APIs Explained](/guides/rest-apis-explained) and
[HTTP & JSON API Basics](/guides/http-and-json-api-basics) guides cover the protocol side; here we focus
on the Jakarta side.

The running example is the `Product` from Phase 3 - four fields, a plain Java object:

```java
public class Product {
    private Long id;
    private String name;
    private BigDecimal price;
    private String sku;
    // constructor, getters, and setters omitted for brevity
}
```

*What just happened:* That's the thing we'll move across the wire - an `id`, a `name`, a `price`, and a
`sku` (the stock-keeping unit, a product's unique catalog code). Nothing Jakarta-specific about it yet;
it's an ordinary object, the same one `ProductService` already manages.

## The `Application` class and your first resource

Two pieces bootstrap a JAX-RS API. First, an **`Application` class** turns the feature on and picks the
base path. You write it once, and it's usually empty:

```java
import jakarta.ws.rs.ApplicationPath;
import jakarta.ws.rs.core.Application;

@ApplicationPath("/api")
public class RestConfig extends Application {
    // empty on purpose - its presence is the configuration
}
```

*What just happened:* `@ApplicationPath("/api")` told the server "activate JAX-RS, and every endpoint
lives under `/api`." Extending `Application` with no body is the standard "scan for my resources
automatically" signal - the server finds your resource classes for you. You don't register anything by
hand; the annotation plus the empty subclass is the whole setup.

📝 Second, a **resource class** is where endpoints actually live. You mark it with `@Path` to give it a
URL, and its methods become the handlers. Crucially, **a JAX-RS resource is also a CDI bean** - so the
`@Inject` you learned in Phase 3 works right here. That's how the resource gets hold of `ProductService`.

```java
import jakarta.inject.Inject;
import jakarta.ws.rs.Path;

@Path("/products")
public class ProductResource {

    @Inject
    private ProductService service;   // the CDI bean from Phase 3, injected for us
}
```

*What just happened:* `@Path("/products")` mapped this class to the URL `/api/products` - the
`/api` from the `Application` class, then `/products` from here. `@Inject ProductService service` is pure
CDI: the container hands this resource a fully-wired `ProductService`, the same way it would any other
bean. This is the Jakarta parallel to Spring's `@RestController` with a constructor-injected service - 
the resource speaks HTTP, the service holds the logic.

## HTTP method annotations

The class has a URL; now its methods need to say *which HTTP verb* they answer and *what format* they
speak. Each handler gets a method annotation (`@GET`, `@POST`, `@PUT`, `@DELETE`) plus content-type
annotations.

📝 `@GET`/`@POST`/`@PUT`/`@DELETE` map a method to that HTTP method. `@Produces` declares the format the
method *sends back*; `@Consumes` declares the format it *accepts*. For a JSON API both are
`MediaType.APPLICATION_JSON`. Here's a `@GET` that returns the whole list of products:

```java
import jakarta.ws.rs.GET;
import jakarta.ws.rs.Path;
import jakarta.ws.rs.Produces;
import jakarta.ws.rs.core.MediaType;
import java.util.List;

@Path("/products")
public class ProductResource {

    @Inject
    private ProductService service;

    @GET
    @Produces(MediaType.APPLICATION_JSON)
    public List<Product> listProducts() {
        return service.findAll();   // delegate to the CDI service; it owns the logic
    }
}
```

*What just happened:* `@GET` said "handle `GET` requests to this resource's path." `@Produces(APPLICATION_JSON)`
said "my response is JSON." The method returns a `List<Product>` - and because the response type is JSON,
the server hands that list to **JSON-B** (more on it shortly), which turns each `Product` into a JSON
object automatically. You wrote no serialization code; you delegated the *work* to `service.findAll()`
and the *formatting* to JSON-B.

A request and the response it produces:

```http
GET /api/products HTTP/1.1
Host: localhost:8080
```

```json
[
  { "id": 1, "name": "Mechanical Keyboard", "price": 129.99, "sku": "KB-MECH-01" },
  { "id": 2, "name": "USB-C Hub", "price": 49.50, "sku": "HUB-USBC-07" }
]
```

*What just happened:* The `List<Product>` came back as a JSON array, one object per product, each field
mapped by name. That field-by-field translation is JSON-B doing its job - your method just returned plain
Java objects and let the spec handle the wire format.

💡 The HTTP verb carries the meaning, so you don't put it in the URL. `GET /api/products` *reads*;
`POST /api/products` *creates*; `DELETE /api/products/2` *removes*. Same noun (`products`), different
verbs - that's the REST convention from [REST APIs Explained](/guides/rest-apis-explained), and JAX-RS
leans on it directly.

## Path and query params

A real API needs to address *one specific product* and to *filter* a list. Those are two different jobs,
and JAX-RS has a different tool for each.

📝 A **path param** is part of the URL path itself - `/api/products/2` means "the product whose id is 2."
You write a placeholder in `@Path` with braces (`@Path("/{id}")`) and bind it with `@PathParam`. A
**query param** is a value after the `?` - `/api/products?maxPrice=50` - and you bind it with
`@QueryParam`. The rule of thumb: a path param *identifies a resource*; a query param *filters or
modifies* a request.

```java
import jakarta.ws.rs.*;
import jakarta.ws.rs.core.MediaType;
import java.math.BigDecimal;
import java.util.List;

@Path("/products")
public class ProductResource {

    @Inject
    private ProductService service;

    @GET
    @Path("/{id}")
    @Produces(MediaType.APPLICATION_JSON)
    public Product getProduct(@PathParam("id") Long id) {
        return service.findById(id);
    }

    @GET
    @Produces(MediaType.APPLICATION_JSON)
    public List<Product> listProducts(@QueryParam("maxPrice") BigDecimal maxPrice) {
        if (maxPrice == null) {
            return service.findAll();
        }
        return service.findCheaperThan(maxPrice);   // filter when ?maxPrice=... is present
    }
}
```

*What just happened:* In `getProduct`, the `{id}` in `@Path("/{id}")` lines up with `@PathParam("id") Long id` - 
the server pulls `2` out of `/api/products/2`, converts the text to a `Long` for you, and passes it in.
In `listProducts`, `@QueryParam("maxPrice")` reads the `?maxPrice=...` query string; when the client
omits it the param is `null`, so we return everything. One noun, two behaviors, driven by the URL.

Try both with curl:

```bash
curl http://localhost:8080/api/products/2
curl "http://localhost:8080/api/products?maxPrice=60"
```

```console
{"id":2,"name":"USB-C Hub","price":49.50,"sku":"HUB-USBC-07"}

[{"id":2,"name":"USB-C Hub","price":49.50,"sku":"HUB-USBC-07"}]
```

*What just happened:* The first call hit the path-param route and returned a single product. The second
hit the same `/api/products` endpoint but, because `?maxPrice=` was present, returned a filtered array
(only the hub came in under 60). The quotes around the second URL keep the shell from choking on the `?`.

## Request bodies and the `Response` object

Reading is half an API. To *create* a product the client sends data in the request body, and to answer
correctly you sometimes need to control the status code yourself.

📝 **JSON-B (Jakarta JSON Binding) is the standard object↔JSON mapper** - the spec's built-in translator,
the role Jackson plays in Spring. It serializes your return values to JSON (the `@GET`s above) and
deserializes incoming JSON into Java objects. So a `@POST` method that takes a `Product` parameter gets a
fully-populated object: JSON-B read the body and filled the fields before your code ran.

That covers the body. For the *status code*, returning a bare object always yields **200 OK**, which
isn't always the truth. Creating a resource should report **201 Created**; a missing product should
report **404 Not Found**. To say exactly which status (and which headers), you return a **`Response`**
instead of the plain object.

```java
import jakarta.ws.rs.*;
import jakarta.ws.rs.core.MediaType;
import jakarta.ws.rs.core.Response;
import jakarta.ws.rs.core.UriInfo;
import jakarta.ws.rs.core.Context;
import java.net.URI;

@Path("/products")
public class ProductResource {

    @Inject
    private ProductService service;

    @POST
    @Consumes(MediaType.APPLICATION_JSON)
    @Produces(MediaType.APPLICATION_JSON)
    public Response createProduct(Product product, @Context UriInfo uriInfo) {
        Product saved = service.create(product);
        URI location = uriInfo.getAbsolutePathBuilder()
                              .path(String.valueOf(saved.getId()))
                              .build();                       // /api/products/3
        return Response.created(location).entity(saved).build();   // 201 + Location header
    }

    @GET
    @Path("/{id}")
    @Produces(MediaType.APPLICATION_JSON)
    public Response getProduct(@PathParam("id") Long id) {
        Product found = service.findById(id);
        if (found == null) {
            return Response.status(Response.Status.NOT_FOUND).build();   // 404, no body
        }
        return Response.ok(found).build();                              // 200, body is the product
    }
}
```

*What just happened:* `createProduct` takes a `Product` parameter with no annotation - JAX-RS treats the
*unannotated* parameter as the request body, and `@Consumes(APPLICATION_JSON)` tells JSON-B to
deserialize the incoming JSON into it. We hand it to `service.create(...)`, then build a `Response`:
`Response.created(location)` sets status **201** *and* a `Location` header pointing at the new product's
URL, and `.entity(saved)` attaches the body. In `getProduct`, a missing product returns a clean **404**
with no body, while a hit returns **200** with the product. Contrast that with the earlier methods that
returned a bare object and always got 200 - `Response` is the switch you flip when the default status
isn't the correct answer.

The request the client sends:

```json
{
  "name": "Laptop Stand",
  "price": 39.95,
  "sku": "STD-LAP-04"
}
```

*What just happened:* The client posts a product with no `id` - the server assigns that on create. JSON-B
binds `name`, `price`, and `sku` onto a `Product` before `createProduct` runs, so by the time your code
executes you're holding a real, populated Java object, not raw text.

## How it all fits

It's worth stepping back to see the layers, because it's the same shape you'll repeat for every resource.

```mermaid
flowchart TD
  A[HTTP request to /api/products] --> B[JAX-RS matches @Path + verb]
  B --> C[Bind path/query params and JSON-B body]
  C --> D[Resource method calls injected ProductService]
  D --> E[Service holds the logic - JPA next, in Phase 5]
  E --> F[JSON-B serializes the result]
  F --> G[Response with status + body sent back]
```

💡 Notice how *thin* the resource is. The best JAX-RS methods do three things: read the request, hand off
to the injected `ProductService` for the real work, and shape the response. The service is where logic
lives - and in [Phase 5](05-jakarta-persistence.md) that service will swap its in-memory placeholder for
Jakarta Persistence (JPA), reading and writing real database rows. The resource won't change at all;
that's the payoff of keeping the layers separate.

⚠️ **Keep persistence and business logic out of the resource.** It's tempting to drop a database query or
a pricing rule straight into a handler because it "works." It does - until that logic needs testing
(you'd have to fake an HTTP request to exercise it), reusing (a scheduled job can't send itself a
request), or changing (and now every change touches the doorway). A resource that does two jobs becomes
the place every bug hides. HTTP in, HTTP out; delegate the rest to the CDI bean.

📝 One more thing worth knowing: JAX-RS isn't only for *serving* requests. It also has a **Client API**
(`ClientBuilder.newClient()`) for *calling* other services' REST endpoints from your own code - handy
when one service needs to talk to another. We won't use it here, but it's the same spec, the other
direction.

## Recap

1. **JAX-RS is the standard REST spec.** An `@ApplicationPath` `Application` subclass switches it on and
   sets the base path; `@Path` resource classes hold your endpoints - the standards-body counterpart to
   Spring's `@RestController`.
2. **Resources are CDI beans.** `@Inject` works inside a resource, so it gets a fully-wired
   `ProductService` for free - the resource stays a thin translator while the service owns the logic.
3. **Method annotations route by verb and format.** `@GET`/`@POST`/`@PUT`/`@DELETE` pick the HTTP method;
   `@Produces` and `@Consumes` declare JSON in and out.
4. **Path params identify, query params filter.** `@PathParam` binds `{id}` from the path (one specific
   product); `@QueryParam` binds `?maxPrice=...` (filter or modify), and is `null` when omitted.
5. **JSON-B maps objects to and from JSON.** It serializes return values and deserializes the unannotated
   body parameter on a `@POST` - the standard's answer to Jackson.
6. **`Response` controls status and headers.** Return a bare object for the default 200, or a `Response`
   for 201 Created (with a `Location` header), 404 Not Found, and anything else where the status is part
   of the answer. ⚠️ Real persistence lives in the service, arriving next in
   [Phase 5](05-jakarta-persistence.md).

## Quick check

Make sure the core JAX-RS ideas stuck:

```quiz
[
  {
    "q": "What does the @ApplicationPath(\"/api\") class do in a JAX-RS app?",
    "choices": [
      "Bootstraps JAX-RS and sets the base path that all resources live under, so /products becomes /api/products",
      "Connects the application to the database automatically",
      "Defines a single endpoint at /api that returns all resources",
      "Replaces the need for @Path on resource classes"
    ],
    "answer": 0,
    "explain": "An Application subclass annotated with @ApplicationPath activates JAX-RS and declares the base path. Each resource's @Path is then appended to it - @Path(\"/products\") under @ApplicationPath(\"/api\") serves /api/products. It does nothing with the database, and resources still need their own @Path."
  },
  {
    "q": "You want to fetch one specific product by its id from /api/products/2. Which annotation binds that 2 to your method parameter?",
    "choices": [
      "@PathParam, because the id is part of the URL path and identifies a specific resource",
      "@QueryParam, because all URL values use the same annotation",
      "An unannotated parameter, because the id travels in the request body",
      "@GET, because that annotation reads the value for you"
    ],
    "answer": 0,
    "explain": "A value embedded in the path (matched by @Path(\"/{id}\")) is bound with @PathParam - it identifies a resource. @QueryParam is for values after the ? (like ?maxPrice=...), which filter or modify. The unannotated parameter is the JSON body, handled by JSON-B."
  },
  {
    "q": "Your create endpoint returns a plain Product object, and clients always get HTTP 200 even though a resource was created. How do you report 201 Created instead?",
    "choices": [
      "Return a Response, e.g. Response.created(location).entity(saved).build(), which carries the status and a Location header alongside the body",
      "Add @POST(status = 201) to the method",
      "Throw an exception after saving so the server picks a different code",
      "Nothing can change it - JAX-RS methods can only return 200"
    ],
    "answer": 0,
    "explain": "Returning a bare object gives the default 200. To control the status (and headers), return a Response - Response.created(location) reports 201 Created and sets the Location header to the new resource's URL. The same object gives you 404 via Response.status(Response.Status.NOT_FOUND).build()."
  }
]
```


---

# Jakarta Persistence (JPA)

In Phase 4 your `ProductResource` happily served JSON, but every product was hand-built or kept in a
list. Now we make them real - rows in a database, fetched and saved through JPA. If you've done any
Hibernate, almost all of this will feel familiar, and that's the point: **Jakarta Persistence *is* the
JPA you already know.** What changes inside a Jakarta EE app server is *who holds the wiring* - this
phase is about that difference, and almost nothing else.

## The mental model: same JPA, the container holds the plumbing

Here's the one idea to anchor everything. In a standalone Hibernate program (the
[Hibernate & JPA guide](/guides/hibernate-and-jpa-from-zero)), *you* build the machine: you create an
`EntityManagerFactory`, open an `EntityManager`, begin and commit transactions, close everything in a
`finally`. You are the plumber.

Inside a Jakarta EE container, the plumbing already exists. The app server (WildFly, Payara, Open
Liberty) reads a small config file, builds the factory, manages a connection pool, and hands you a
ready-to-use `EntityManager` through an annotation. Your job shrinks to: *describe* the persistence
setup once, then *ask* for an `EntityManager` and use it.

> 💡 **Key point.** The JPA *API* - `@Entity`, `persist`, `find`, JPQL, the persistence context, lazy
> loading - is identical. The only EE-specific parts are three things: the **persistence unit**
> (config), the **container-managed `EntityManager`** (injection), and **container-managed
> transactions** (Phase 6). Everything else you already know carries over unchanged.

## JPA is a Jakarta EE spec

📝 **Jakarta Persistence** is one of the specifications bundled into Jakarta EE - the same "spec, not
implementation" idea from [Phase 1](01-what-jakarta-ee-is.md). The spec defines the annotations and the
`EntityManager` API; a **provider** supplies the actual engine. **Hibernate is the most common
provider** (EclipseLink is the other big one), and most app servers ship one by default. So "JPA" and
"the Hibernate you may know" are not competitors - JPA is the standard, Hibernate is the thing under it.

The entity mapping itself is plain JPA. Here's our `Product`, mapped the same way you'd map anything in
the standalone guide:

```java
import jakarta.persistence.*;
import java.math.BigDecimal;

@Entity
@Table(name = "products")
public class Product {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    private String name;
    private BigDecimal price;

    @Column(unique = true)
    private String sku;

    protected Product() {}   // JPA needs a no-arg constructor

    public Product(String name, BigDecimal price, String sku) {
        this.name = name;
        this.price = price;
        this.sku = sku;
    }

    // getters and setters omitted
}
```
*What just happened:* This is ordinary JPA mapping - `@Entity` marks the class as a table-backed type,
`@Id` + `@GeneratedValue` make the database assign the primary key, and `@Column(unique = true)` mirrors
a `UNIQUE` constraint on `sku`. Nothing here is Jakarta-EE-specific. For the full story on mapping
(relationships, embeddables, inheritance), lean on the
[Hibernate & JPA guide](/guides/hibernate-and-jpa-from-zero) - we won't re-teach it. What's new starts
with *where this entity gets registered*.

## The persistence unit & `persistence.xml`

📝 A **persistence unit** is a named bundle that answers three questions: *which provider* runs the
show, *which datasource* (database connection) to use, and *which entities* belong to it. You declare it
in a file called `persistence.xml`, which lives at `src/main/resources/META-INF/persistence.xml` in your
WAR.

```xml
<?xml version="1.0" encoding="UTF-8"?>
<persistence xmlns="https://jakarta.ee/xml/ns/persistence"
             version="3.0">

    <persistence-unit name="storePU" transaction-type="JTA">
        <jta-data-source>java:/jdbc/StoreDS</jta-data-source>
        <properties>
            <property name="jakarta.persistence.schema-generation.database.action"
                      value="update"/>
        </properties>
    </persistence-unit>

</persistence>
```
*What just happened:* We named one persistence unit `storePU`. The big EE difference is
`<jta-data-source>java:/jdbc/StoreDS</jta-data-source>`: instead of putting a JDBC URL, username, and
connection-pool settings here, we point at a **JNDI name** - a datasource the *app server* already
defined and manages. You configure `StoreDS` once in the server (its URL, credentials, pool size), and
every app just references it by name. `transaction-type="JTA"` says "let the container run the
transactions" (Phase 6). We didn't even name a provider - the server's default (often Hibernate) is
assumed.

> 📝 **Where did the connection pool go?** In standalone Hibernate you'd configure the JDBC URL and a
> pool (HikariCP, c3p0) yourself. In EE that's the *server's* job. The datasource is infrastructure the
> container owns; your app borrows it by JNDI name. One less thing you wire, one less thing that differs
> between dev and prod.

## Container-managed `EntityManager`

This is the heart of the phase. In the standalone guide you wrote `emf.createEntityManager()` and were
responsible for closing it. In EE, you write one annotation and the container does the rest:

```java
import jakarta.ejb.Stateless;
import jakarta.persistence.EntityManager;
import jakarta.persistence.PersistenceContext;
import java.math.BigDecimal;
import java.util.List;

@Stateless
public class ProductService {

    @PersistenceContext(unitName = "storePU")
    private EntityManager em;

    public Product create(String name, BigDecimal price, String sku) {
        Product product = new Product(name, price, sku);
        em.persist(product);          // schedules the INSERT
        return product;               // managed; id is populated after flush
    }

    public Product find(Long id) {
        return em.find(Product.class, id);   // SELECT by primary key
    }

    public List<Product> all() {
        return em.createQuery("SELECT p FROM Product p", Product.class)
                 .getResultList();           // JPQL - same as ever
    }
}
```
*What just happened:* `@PersistenceContext(unitName = "storePU")` tells the container: "inject an
`EntityManager` bound to the `storePU` unit." The container creates it, wires it to the datasource, and
manages its whole lifecycle - **you never call `createEntityManager` and you never call `em.close()`.**
From there, `em.persist`, `em.find`, and `createQuery` behave *exactly* as they do in the
[EntityManager phase](/guides/hibernate-and-jpa-from-zero) of the Hibernate guide: `persist` makes a
transient `Product` managed and schedules an `INSERT`, `find` hits the first-level cache then the
database, and the persistence context is still the same per-transaction identity map you already
understand.

> ⚠️ Don't reach for `EntityManagerFactory.createEntityManager()` inside a managed bean. That's the
> standalone pattern - in a container it gives you an *unmanaged* `EntityManager` you'd have to close
> yourself, and it won't join the container's transaction. In EE, `@PersistenceContext` is the way.

A `ProductResource` from [Phase 4](04-jax-rs-rest-apis.md) just injects this service and calls it - the
resource stays thin, the service owns persistence:

```java
@Path("/products")
public class ProductResource {

    @Inject
    private ProductService products;     // CDI injection, from Phase 3

    @POST
    @Consumes(MediaType.APPLICATION_JSON)
    public Product create(Product input) {
        return products.create(input.getName(), input.getPrice(), input.getSku());
    }

    @GET
    @Path("/{id}")
    public Product get(@PathParam("id") Long id) {
        return products.find(id);
    }
}
```
*What just happened:* The JAX-RS resource handles HTTP and JSON (Phase 4); the `ProductService` handles
persistence. `@Inject` (CDI, [Phase 3](03-cdi-dependency-injection.md)) hands the resource a live
`ProductService` whose `em` is already wired. This is the standard EE layering: **resource → service →
`@PersistenceContext em` → provider → database.**

## Transactions are container-managed (a preview)

Notice what's *missing* from `ProductService.create`: there's no `em.getTransaction().begin()` and no
`commit()`. In the standalone Hibernate guide those calls were mandatory - forget the commit and nothing
saved. Here they're gone on purpose.

📝 Because we marked the bean `@Stateless` and the unit `transaction-type="JTA"`, the **container wraps
each public method in a transaction automatically.** When `create` is called, the container starts a
transaction; when the method returns normally, the container commits (and the scheduled `INSERT`
flushes); if the method throws, the container rolls back. Your `em.persist` call *joins* whatever
transaction the container has running.

That's why the `id` is populated and the row is saved even though you never wrote a single transaction
line. The full mechanics - `@Transactional`, rollback rules, what JTA coordinates across multiple
resources - are [Phase 6](06-transactions-with-jta.md). For now, the takeaway is just: **in EE you
describe boundaries with annotations, you don't hand-code begin/commit.**

## Hibernate underneath - reuse everything you know

💡 Step back and see how little is actually new. The request path through your app is:

```mermaid
flowchart LR
  A[JAX-RS resource] --> B[CDI service]
  B --> C["@PersistenceContext EntityManager"]
  C --> D["JPA provider (often Hibernate)"]
  D --> E[(Database via JNDI datasource)]
```

Of that whole chain, the *only* EE-specific links are the persistence unit, the injected
`EntityManager`, and the container transaction. Everything to the right of the `EntityManager` is plain
JPA running on plain Hibernate - which means **everything from the
[Hibernate & JPA guide](/guides/hibernate-and-jpa-from-zero) still applies, byte for byte:**
relationships and `@OneToMany`, JPQL and the Criteria API, lazy vs eager fetching, the persistence
context as identity map and first-level cache, dirty checking, and the N+1 problem.

⚠️ Which also means the *traps* come along unchanged. The container injecting your `EntityManager`
doesn't make N+1 disappear - loop over 100 products touching a lazy relationship and you'll fire 100
extra `SELECT`s, same as anywhere. Lazy-loading still needs an open persistence context, so reaching for
an un-fetched relationship after the transaction ends still throws `LazyInitializationException`. The
fix is the same one the Hibernate guide teaches: watch the SQL your provider emits (turn on SQL logging
in dev), fetch what you need with a `JOIN FETCH`, and don't trust that "it's a managed `EntityManager`
now" changes any of the performance rules. The container manages the *lifecycle*, not the *queries* - 
those are still yours to get right.

## Recap

1. **Jakarta Persistence is a spec**; **Hibernate is the usual provider** under it. The JPA API
   (`@Entity`, `persist`, `find`, JPQL) is the same one you learn in the
   [Hibernate & JPA guide](/guides/hibernate-and-jpa-from-zero).
2. A **persistence unit**, declared in `persistence.xml`, names the provider, the **datasource (by JNDI
   name)**, and the entities. The app server owns the connection pool - you reference it, you don't wire
   it.
3. **`@PersistenceContext EntityManager em`** gives you a **container-managed** `EntityManager`: the
   container creates it, wires it, and closes it. You never call `createEntityManager` or `em.close()`.
4. Transactions are **container-managed** - no `begin`/`commit` by hand. `em` operations join the
   container's transaction automatically; rollback on a thrown exception. Full detail in
   [Phase 6](06-transactions-with-jta.md).
5. ⚠️ The container manages the EntityManager's *lifecycle*, not your *queries*. **N+1, lazy-loading
   pitfalls, and `LazyInitializationException` all still apply** - watch the SQL.

## Quick check

The three things that are actually different about JPA in a container:

```quiz
[
  {
    "q": "What does `@PersistenceContext EntityManager em;` give you inside a Jakarta EE managed bean?",
    "choices": [
      "A container-managed EntityManager - the container creates, wires, and closes it; you never call createEntityManager or em.close()",
      "A new EntityManagerFactory you must call createEntityManager() on",
      "A second-level cache instance shared across all requests",
      "A raw JDBC connection you manage by hand"
    ],
    "answer": 0,
    "explain": "In EE the container injects and manages the EntityManager's whole lifecycle. That's the main difference from standalone Hibernate, where you'd build the factory, open the EntityManager, and close it yourself."
  },
  {
    "q": "In an EE persistence.xml, why do you point at a JNDI name like `java:/jdbc/StoreDS` instead of a JDBC URL and pool settings?",
    "choices": [
      "Because the app server owns the datasource and connection pool; the persistence unit just references it by JNDI name",
      "Because JPA cannot read JDBC URLs at all",
      "Because the JNDI name is the database password in disguise",
      "Because each entity needs its own separate connection"
    ],
    "answer": 0,
    "explain": "The container manages the datasource (URL, credentials, pool). You configure it once in the server and every app references it by JNDI name - one less thing your app wires, and it stays consistent across environments."
  },
  {
    "q": "You inject a container-managed EntityManager and loop over products touching a lazy relationship. What happens to the N+1 problem?",
    "choices": [
      "It still happens - container management handles lifecycle, not query efficiency; you still need JOIN FETCH and to watch the SQL",
      "It disappears, because container-managed EntityManagers auto-batch all queries",
      "It throws a compile error before the loop runs",
      "It's impossible, because JTA prevents extra SELECTs"
    ],
    "answer": 0,
    "explain": "Everything to the right of the EntityManager is plain JPA on plain Hibernate, so N+1, lazy-loading, and LazyInitializationException all apply unchanged. The container manages the EntityManager's lifecycle, not your queries."
  }
]
```


---

# Transactions with JTA

In [Phase 5](05-jakarta-persistence.md) you noticed something quietly missing from `ProductService.create`: there was no `em.getTransaction().begin()` and no `commit()`. The `INSERT` still happened, the `id` still came back, the row was still there. This phase explains that disappearing act - the single biggest day-to-day difference between writing JPA standalone and writing it inside a Jakarta EE container.

Here's the whole phase in one sentence, worth pinning to your monitor:

> **In standalone JPA *you* bracket the work with begin/commit; in Jakarta EE you put one annotation on a method and the container opens the transaction before it runs and commits after - rolling back if you throw.**

That shift - from imperative (you call begin/commit) to **declarative** (you describe boundaries with an annotation) - is what we unpack below. Everything about *what a transaction is* you already know from [Transactions & ACID](/guides/transactions-and-acid) and the [Hibernate guide's transactions phase](/guides/hibernate-and-jpa-from-zero). What's new is *who pulls the levers*.

## The EE difference: declarative transactions

📝 In standalone JPA you write the three calls by hand - `tx.begin()`, your work, `tx.commit()`, with a `rollback()` in the `catch`. You saw exactly that shape in the [Hibernate guide's Phase 4](/guides/hibernate-and-jpa-from-zero): open the bracket, do the work, close it, undo on failure. It works, but every service method repeats the same boilerplate, and forgetting the commit means your changes silently vanish.

In Jakarta EE, the **container** owns that bracket. You annotate a CDI bean method with `@Transactional` and the container does the begin/commit/rollback *around* your method - like an invisible `try`/`commit`/`catch`/`rollback` wrapped over the whole call. Your code inside only describes the work.

```java
import jakarta.enterprise.context.ApplicationScoped;
import jakarta.transaction.Transactional;
import jakarta.persistence.EntityManager;
import jakarta.persistence.PersistenceContext;
import java.math.BigDecimal;

@ApplicationScoped
public class ProductService {

    @PersistenceContext(unitName = "storePU")
    private EntityManager em;

    @Transactional
    public Product create(String name, BigDecimal price, String sku) {
        Product product = new Product(name, price, sku);
        em.persist(product);     // joins the container's transaction
        return product;          // returns normally -> container commits
    }
}
```
*What just happened:* `@Transactional` (from `jakarta.transaction`) tells the container: "start a transaction before `create` runs, commit it when `create` returns normally, roll it back if `create` throws." The `em.persist` call doesn't start anything - it *joins* the transaction the container already opened. When the method returns, the container commits, the persistence context flushes, and the `INSERT` hits the database. There is no begin, no commit, no `finally` in your code because the container wrote all of it for you. This is the same atomicity guarantee from [Transactions & ACID](/guides/transactions-and-acid) - all-or-nothing - just declared instead of hand-coded.

💡 If you've used Spring, `@Transactional` will feel instantly familiar - same idea, same name, near-identical behavior. (Different package: Jakarta's is `jakarta.transaction.Transactional`; Spring's is `org.springframework.transaction.annotation.Transactional`.) Both lean on the container/framework to write begin/commit for you.

## What JTA actually is

You've now seen *who* runs the transaction (the container) but not *with what*. The answer is JTA.

📝 **Jakarta Transactions (JTA)** is the standard transaction API and transaction *manager* that the Jakarta EE container uses under the hood. When `@Transactional` says "start a transaction," it's the JTA transaction manager that actually starts, tracks, and commits it. You rarely call JTA directly - `@Transactional` is the friendly face over it - but it's the machinery doing the work.

Here's JTA's superpower, the thing a plain single-connection transaction *cannot* do:

💡 A local transaction lives inside **one** database connection - begin, work, commit, all on that single connection. JTA can span **multiple resources** in one atomic transaction: two different databases, or a database *plus* a message queue. Write a `Product` row to the orders DB and publish an "order placed" message to a queue, and JTA can make both commit together or both roll back together - even though they're entirely separate systems. That coordination across resources is exactly what a local transaction can't give you, and it's why EE containers ship a JTA transaction manager.

```java
@Transactional
public void placeOrder(Product product, BigDecimal price, String sku) {
    em.persist(product);                 // resource 1: the database
    orderEvents.send(sku, price);        // resource 2: a message queue (JMS)
    // both commit together, or if either throws, BOTH roll back
}
```
*What just happened:* Inside one `@Transactional` method we touched two different resources - the database via `em` and a message queue via `orderEvents`. Because JTA is coordinating, they're enrolled in the *same* transaction. If the queue send fails after the `persist`, JTA rolls back the database insert too, so you never end up with a saved product and a lost "order placed" event (or vice versa). Spanning resources like this is the headline reason JTA exists - and, as we'll see at the end, it's not free.

## Transaction attributes: what happens when methods call methods

A `@Transactional` method often calls *another* `@Transactional` method. Does the inner one join the outer transaction, start its own, or refuse to run without one? That's what **transaction attributes** decide. You set them with `@Transactional(value = ...)`.

📝 The ones you'll actually use:

- **`REQUIRED`** *(the default)* - join the caller's transaction if one exists; start a new one if not. The sensible default: nested calls all share one transaction.
- **`REQUIRES_NEW`** - always start a *fresh*, independent transaction. If the caller already had one, it gets **suspended** until this new one finishes, then resumes. The inner transaction commits or rolls back on its own, independent of the outer.
- **`MANDATORY`** - there *must* already be a transaction; if the method is called without one, it throws. ("I refuse to run unbracketed.")
- **`SUPPORTS`** - run inside a transaction if one exists, otherwise run without one. ("I don't care either way.")
- **`NEVER`** - there must be *no* transaction; throws if one is active. ("I refuse to run inside a transaction.")

The interesting pair is `REQUIRED` vs `REQUIRES_NEW`, because they behave differently when one service calls another:

```java
import jakarta.transaction.Transactional;
import static jakarta.transaction.Transactional.TxType.REQUIRES_NEW;

@ApplicationScoped
public class ProductService {

    @Inject AuditService audit;

    @Transactional                       // REQUIRED (default): the main transaction
    public Product create(String name, BigDecimal price, String sku) {
        Product product = new Product(name, price, sku);
        em.persist(product);
        audit.record("created " + sku);  // runs in its OWN transaction
        return product;
    }
}

@ApplicationScoped
public class AuditService {

    @Transactional(REQUIRES_NEW)         // suspend the caller, start a fresh one
    public void record(String message) {
        em.persist(new AuditEntry(message));
    }
}
```
*What just happened:* `create` runs under the default `REQUIRED`, so it opened (or joined) the main transaction. When it calls `audit.record`, that method is `REQUIRES_NEW` - so the container **suspends** `create`'s transaction, runs `record` in a brand-new independent one, commits *that*, and then resumes `create`'s transaction. The practical consequence: the audit entry commits separately. If `create` later throws and rolls back, the product insert is undone but the audit entry *stays* - because it committed in its own transaction. That's usually exactly what you want for audit logs (you want the record even when the operation fails), and it's exactly the wrong choice for work that must succeed or fail *with* the caller. Picking the attribute is picking who shares fate with whom.

## Rollback rules: the checked-exception trap

Now the gotcha that bites everyone once. You'd assume "throw an exception, the transaction rolls back." That's true - but only for *some* exceptions.

⚠️ By default, `@Transactional` rolls back on **unchecked** exceptions (`RuntimeException` and its subclasses, plus `Error`) but **commits** on **checked** exceptions. So if your method throws a checked exception you wrote - say `InsufficientStockException extends Exception` - the container assumes it's a recoverable business condition you handled deliberately and **commits the transaction anyway.** Your half-finished work becomes permanent, which is almost never what you meant.

```java
// THE TRAP - checked exception does NOT roll back by default
@Transactional
public void buy(String sku) throws InsufficientStockException {
    Product p = em.find(Product.class, lookupId(sku));
    em.persist(new Reservation(sku));          // change 1
    if (p.getStock() <= 0) {
        throw new InsufficientStockException(); // checked -> COMMITS anyway!
    }
    p.decrementStock();                         // never reached
}
```
*What just happened:* We threw a checked exception to signal "out of stock," expecting the `Reservation` insert to be undone. But because `InsufficientStockException` is checked, the container's default rule **commits** - leaving a phantom reservation for a product you couldn't sell. The fix is to tell `@Transactional` explicitly which exceptions should roll back (or shouldn't), with `rollbackOn` / `dontRollbackOn`:

```java
@Transactional(rollbackOn = InsufficientStockException.class)
public void buy(String sku) throws InsufficientStockException {
    // ...same body... now the checked exception triggers a rollback
}
```
*What just happened:* `rollbackOn = InsufficientStockException.class` overrides the default and says "this checked exception *should* roll back too." Now throwing it undoes the `Reservation`. The mirror-image knob is `dontRollbackOn` - "even though this is a runtime exception, commit anyway." 💡 Spring's `@Transactional` has the *exact same* default and the *exact same* trap (it uses `rollbackFor` / `noRollbackFor` for the same job). So this isn't a Jakarta quirk - it's a cross-ecosystem footgun worth memorizing: **checked exceptions don't roll back unless you say so.**

## The unit-of-work tie-in: one transaction, one persistence context

Step back and connect this to the [Hibernate guide's unit of work](/guides/hibernate-and-jpa-from-zero). 💡 The container's transaction doesn't just bracket your `em` calls - it *scopes the persistence context*. The whole flush-at-commit, dirty-checking machinery you learned standalone runs at the **container's** commit point now. Change a managed entity's field with no `save` call, and the `UPDATE` fires when the container commits at the end of your `@Transactional` method. Same magic, different trigger.

So the full request flow ties together every phase of this guide:

```mermaid
flowchart LR
  A[JAX-RS resource] --> B["@Transactional CDI service"]
  B --> C["em.persist / em.find / dirty checking"]
  C --> D[container commits at method return]
  D --> E[(Database via JNDI datasource)]
```

A JAX-RS resource (Phase 4) calls a `@Transactional` service method (this phase); inside, your `em` operations (Phase 5) all enroll in the one container transaction; when the method returns, the container commits and the persistence context flushes - everything stuck or everything rolled back, as one unit of work. The resource stays thin, the service owns the transaction boundary, and dirty checking just works because the entity stays managed for the whole method.

⚠️ A closing caution on JTA's superpower. Spanning multiple resources atomically uses **two-phase commit (XA)**: the transaction manager asks every resource "ready to commit?" (phase 1), and only if *all* say yes does it tell them "commit now" (phase 2). It's genuinely powerful, but it's *costly* - extra round-trips, held locks, and more failure modes than a single-resource commit. Don't span resources out of habit. Reach for distributed/XA transactions only when you truly need atomicity *across* systems (the DB-plus-queue case). When one database is all you're touching, a single-resource transaction is faster, simpler, and far less to go wrong.

## Recap

1. **Declarative, not imperative** - standalone JPA makes you write begin/commit; Jakarta EE wraps a CDI bean method in a transaction when you annotate it `@Transactional`. The container opens it before the method, commits on normal return, rolls back on throw.
2. **JTA is the engine** - Jakarta Transactions is the standard transaction manager the container drives. Its superpower over a local (single-connection) transaction is coordinating **multiple resources** (two DBs, or a DB + a queue) atomically.
3. **Transaction attributes decide nesting** - `REQUIRED` (default: join or start) and `REQUIRES_NEW` (suspend caller, start an independent one) are the common pair; `MANDATORY`, `SUPPORTS`, and `NEVER` round it out. They control who shares fate when methods call methods.
4. **The rollback trap** - by default it rolls back on **unchecked** exceptions but **commits** on **checked** ones. Use `rollbackOn` / `dontRollbackOn` to override. Spring has the identical gotcha (`rollbackFor` / `noRollbackFor`).
5. **One transaction = one unit of work** - the container transaction scopes the persistence context, so dirty checking and flush happen at the container's commit. Reserve distributed/XA (two-phase commit) for when you genuinely need atomicity across resources - it's powerful but costly.

## Quick check

The one idea that must stick: the container runs the transaction, and the rollback rule has a sharp edge.

```quiz
[
  {
    "q": "You put @Transactional on a CDI bean method, call em.persist inside it, and the method returns normally - never calling begin or commit. What happens?",
    "choices": [
      "The container opened a transaction before the method and commits it on normal return, so the INSERT is saved",
      "Nothing is saved - without an explicit commit() the persist is discarded",
      "It throws, because em.persist requires a manual transaction in EE",
      "The row is written immediately when persist runs, before the method returns"
    ],
    "answer": 0,
    "explain": "@Transactional makes the container bracket the method: begin before, commit on normal return, rollback on throw. Your em.persist just joins that transaction, so the INSERT flushes and commits when the method returns - no hand-written begin/commit needed."
  },
  {
    "q": "Your @Transactional method throws a CHECKED exception (one extending Exception) after an em.persist. With default settings, what happens to the persisted row?",
    "choices": [
      "It commits - by default @Transactional only rolls back on unchecked exceptions; use rollbackOn to roll back on a checked one",
      "It always rolls back - any thrown exception undoes the transaction",
      "It throws a second exception because checked exceptions are illegal in transactions",
      "It commits the row but logs a warning, then rolls back on the next call"
    ],
    "answer": 0,
    "explain": "The default rule rolls back on unchecked (RuntimeException/Error) but COMMITS on checked exceptions. That's the classic trap - your half-finished work becomes permanent. Add rollbackOn = YourException.class to force a rollback. Spring's @Transactional behaves the same way."
  },
  {
    "q": "Method A is @Transactional (default REQUIRED) and calls method B, which is @Transactional(REQUIRES_NEW). A later throws and rolls back. What happens to B's work?",
    "choices": [
      "B's work stays committed - REQUIRES_NEW suspended A's transaction and ran B in an independent one that already committed",
      "B's work rolls back too, since it was called from within A",
      "B never ran, because REQUIRES_NEW blocks calls from an active transaction",
      "Both A and B stay committed, because REQUIRES_NEW disables rollback"
    ],
    "answer": 0,
    "explain": "REQUIRES_NEW suspends the caller's transaction and runs B in a fresh, independent one that commits on its own. So when A later rolls back, B's committed work survives - which is why REQUIRES_NEW suits audit logging but is wrong for work that must fail together with the caller."
  }
]
```


---

# Validation & JSON Binding

In [Phase 4](04-jax-rs-rest-apis.md) you built a JAX-RS resource that takes an incoming `Product` from a
request body and hands it to your service. There's a quiet assumption buried in that code: that the
`Product` arriving over the wire is *sane*. A real client will eventually send a product with a blank
name, a negative price, or a `sku` that's a single character - by accident or on purpose. Where do you
catch that?

The mental model to hold onto for this whole phase: **two standards bracket your method, one on each side
of the wire.** On the way *in*, raw JSON bytes arrive and something has to turn them into a `Product`
object - that's **JSON-B**, the binding spec. Then, before your code runs, something checks that the
object actually makes sense - that's **Bean Validation**, the constraint spec. On the way *out*, JSON-B
runs again, turning your `Product` back into JSON. You don't call either one by hand inside a JAX-RS
resource; you *declare* what you want with annotations and let the container do the work. Same inversion of
control you've seen all guide: you label, the framework acts.

📝 If you've used Spring's `@Valid` and `@NotBlank`
([The Service Layer, DTOs & Validation](/guides/spring-boot-from-zero)),
you already know this - because it's the *same spec*. Bean Validation is a Jakarta standard; Spring
implements it too. The annotations (`@NotBlank`, `@Size`, `@Positive`) are identical down to the package
name. What changes here is the *wiring*: instead of a Spring `@RestController`, the rules plug into a
JAX-RS resource, and instead of Jackson, the JSON mapper is JSON-B.

## Jakarta Bean Validation - rules that live on the data

📝 **Jakarta Bean Validation is the standard way to declare constraints with annotations directly on your
fields.** Instead of a pile of `if` statements scattered across your methods, you write the rule *once*,
right next to the field it describes, and the constraint travels with the data wherever it goes. The
common constraints:

- `@NotNull` - the value must not be null.
- `@NotBlank` - a string must be non-null and contain at least one non-whitespace character.
- `@Size(min=, max=)` - a string (or collection) length must fall in range.
- `@Min` / `@Max` - a number must be at least / at most a value.
- `@Positive` / `@PositiveOrZero` - a number must be greater than (or equal to) zero.
- `@Email` - a string must look like an email address.

Here's the `Product` from Phase 4, now wearing its rules:

```java
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.Positive;
import jakarta.validation.constraints.Size;
import jakarta.validation.constraints.NotNull;
import java.math.BigDecimal;

public class Product {

    private Long id;

    @NotBlank(message = "name is required")
    @Size(max = 100, message = "name must be at most 100 characters")
    private String name;

    @NotNull(message = "price is required")
    @Positive(message = "price must be greater than zero")
    private BigDecimal price;

    @Size(min = 3, max = 32, message = "sku must be 3–32 characters")
    private String sku;

    // constructor, getters, and setters omitted for brevity
}
```

*What just happened:* Each constraint is a declaration of intent sitting on the field it guards. `name`
must be present and non-blank and not absurdly long; `price` must exist and be positive (you don't sell
things for negative money); `sku` must be a reasonable length. Notice the `id` has *no* constraints - it's
assigned by the server on create, not sent by the client, so there's nothing to validate. None of these
annotations *does* anything on its own yet; they're metadata describing what a valid `Product` looks like.
The next section is the switch that makes the container actually enforce them.

## Validating in JAX-RS - `@Valid` at the boundary

📝 **`@Valid` on a resource method's body parameter tells the container to validate the incoming object
before your method body runs.** The flow: JSON-B deserializes the request body into a `Product`, then - 
because of `@Valid` - Bean Validation checks every constraint on it. If anything fails, your code *never
executes*; the container short-circuits and returns a **400 Bad Request** describing the violations. If
everything passes, your method runs with an object you can trust.

```java
import jakarta.validation.Valid;
import jakarta.ws.rs.*;
import jakarta.ws.rs.core.MediaType;
import jakarta.ws.rs.core.Response;

@Path("/products")
public class ProductResource {

    @Inject
    private ProductService service;

    @POST
    @Consumes(MediaType.APPLICATION_JSON)
    @Produces(MediaType.APPLICATION_JSON)
    public Response createProduct(@Valid Product product) {
        Product saved = service.create(product);   // only runs if validation passed
        return Response.status(Response.Status.CREATED).entity(saved).build();
    }
}
```

*What just happened:* The only new token versus Phase 4 is `@Valid` in front of the `Product` parameter.
That single annotation is the switch - it tells JAX-RS "after JSON-B builds this object, run its
constraints, and if any fail, don't call me." The body of `createProduct` is now allowed to assume a clean
product, because it can't be reached any other way. Without `@Valid`, the annotations on `Product` are
inert metadata and a blank-name product would sail straight into `service.create(...)`.

Send a bad request - a blank name and a negative price:

```json
{
  "name": "",
  "price": -5.00,
  "sku": "KB-MECH-01"
}
```

The container rejects it before your code runs, with a 400 and the violations:

```json
{
  "status": 400,
  "violations": [
    { "field": "createProduct.product.name",  "message": "name is required" },
    { "field": "createProduct.product.price", "message": "price must be greater than zero" }
  ]
}
```

*What just happened:* You wrote zero `if` statements, yet the request was rejected with a clear 400 listing
exactly which fields failed and the `message` strings you authored on the constraints. The exact JSON shape
of this error body varies by application server (WildFly, Payara, and Open Liberty differ in the wrapper
and the `field` naming), but the principle is universal: violations come back as a 400 before your handler
is entered. In practice you'd map these to a tidy, consistent shape with an `ExceptionMapper` - the JAX-RS
hook for turning exceptions into responses - but the default already protects you.

💡 This is the Jakarta mirror of Spring's `@Valid @RequestBody` - same annotation, same 400-on-failure
behavior, different doorway. The skill transfers directly.

## Validating elsewhere - not just at the HTTP edge

The HTTP boundary is the *most common* place to validate, but it isn't the only one. `@Valid` and the
constraint annotations work anywhere the container manages the call.

📝 You can put `@Valid` (and even bare constraints) on **CDI or EJB method parameters**, and the container
will validate them on every call - this is *method-level validation*. A `ProductService.create` can demand
a valid argument no matter who calls it, not just HTTP traffic:

```java
import jakarta.validation.Valid;

@ApplicationScoped
public class ProductService {

    public Product create(@Valid Product product) {   // validated on every call, HTTP or not
        // ... persist it
        return product;
    }
}
```

*What just happened:* The `@Valid` here means a scheduled job, a message listener, or another service that
calls `create` gets the *same* guarantee the HTTP layer does - the container validates the argument before
the method body runs. The rule no longer depends on someone remembering to validate at the edge.

You can also validate **programmatically** when you need full control - inject a `Validator` and ask it
directly:

```java
import jakarta.validation.Validator;
import jakarta.validation.ConstraintViolation;
import jakarta.inject.Inject;
import java.util.Set;

@Inject
private Validator validator;

public void check(Product product) {
    Set<ConstraintViolation<Product>> violations = validator.validate(product);
    if (!violations.isEmpty()) {
        // inspect each violation, decide what to do
    }
}
```

*What just happened:* `validator.validate(product)` runs the constraints and hands back a `Set` of whatever
failed (empty means valid). This is the manual escape hatch for when annotations on a parameter aren't
enough - say you need to validate conditionally, or collect violations to report in a custom way. Most of
the time `@Valid` is all you need; reach for the `Validator` only when you genuinely need to drive
validation yourself.

## JSON-B - mapping objects to and from JSON

You've leaned on JSON-B since Phase 4 without configuring it. Time to take the wheel.

📝 **JSON-B (Jakarta JSON Binding) is the standard object↔JSON mapper** - the spec's built-in translator,
the role Jackson plays in the Spring world. JAX-RS uses it automatically: it serializes your return values
to JSON and deserializes incoming JSON into Java objects. Out of the box it maps field names straight
across. When the JSON shape needs to differ from your Java shape, a few annotations adjust it:

- `@JsonbProperty("name")` - use a different JSON key for this field.
- `@JsonbTransient` - exclude this field from JSON entirely.
- `@JsonbDateFormat` / `@JsonbNumberFormat` - control how dates and numbers are rendered.

```java
import jakarta.json.bind.annotation.JsonbProperty;
import jakarta.json.bind.annotation.JsonbTransient;
import java.math.BigDecimal;

public class Product {

    private Long id;
    private String name;
    private BigDecimal price;

    @JsonbProperty("stockKeepingUnit")   // rename the JSON key
    private String sku;

    @JsonbTransient                       // never serialize this to clients
    private String internalCostCode;

    // getters and setters omitted
}
```

*What just happened:* `@JsonbProperty("stockKeepingUnit")` decouples the wire name from the Java field - 
the JSON key becomes `stockKeepingUnit` while your code still calls it `sku`. `@JsonbTransient` on
`internalCostCode` keeps that field out of the JSON completely, so an internal value can never leak to a
client. Everything else maps by field name as before.

That `Product` serializes to:

```json
{
  "id": 1,
  "name": "Mechanical Keyboard",
  "price": 129.99,
  "stockKeepingUnit": "KB-MECH-01"
}
```

*What just happened:* `sku` came out as `stockKeepingUnit` exactly as the annotation directed, and
`internalCostCode` is nowhere in the output - `@JsonbTransient` did its job. The other fields mapped
straight across. You changed the public contract without touching a single line of serialization code.

📝 One layer below JSON-B sits **JSON-P (Jakarta JSON Processing)** - a low-level API for reading and
writing JSON as a stream or tree (`JsonObject`, `JsonParser`) without binding to Java classes. You reach
for it when you're handling JSON whose shape you don't know ahead of time, or when you want streaming
control. For mapping known objects - which is almost always - JSON-B is the right tool, and it's built on
JSON-P under the hood.

## Custom constraints, and how the pieces snap together

The built-in constraints cover most needs, but sometimes a rule is specific to *your* domain. Say a valid
`sku` must match a particular pattern your catalog uses. You can build your own constraint.

📝 **A custom constraint is an annotation paired with a `ConstraintValidator`.** You define the annotation,
point it at a validator class, and from then on it behaves exactly like `@NotBlank` - usable on any field,
enforced by `@Valid`.

```java
import jakarta.validation.Constraint;
import jakarta.validation.Payload;
import java.lang.annotation.*;

@Constraint(validatedBy = ValidSkuValidator.class)
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface ValidSku {
    String message() default "sku format is invalid";
    Class<?>[] groups() default {};
    Class<? extends Payload>[] payload() default {};
}
```

```java
import jakarta.validation.ConstraintValidator;
import jakarta.validation.ConstraintValidatorContext;

public class ValidSkuValidator implements ConstraintValidator<ValidSku, String> {

    @Override
    public boolean isValid(String value, ConstraintValidatorContext context) {
        if (value == null) return true;   // let @NotNull own the "required" rule
        return value.matches("[A-Z]{2,4}-[A-Z]+-\\d{2}");   // e.g. KB-MECH-01
    }
}
```

*What just happened:* The `@ValidSku` annotation is wired to `ValidSkuValidator` via
`@Constraint(validatedBy = ...)`. The validator's `isValid` returns `true` for a good value and `false` to
trigger a violation - here it checks the SKU pattern. Returning `true` for `null` is the convention: each
constraint should do *one* job, so "is it present?" stays the responsibility of `@NotNull`. Drop `@ValidSku`
on the `sku` field and it now participates in `@Valid` like any built-in rule.

💡 Step back and watch the full request flow, because every standard in this guide has a place in it:

```mermaid
flowchart TD
  A[HTTP request with JSON body] --> B[JSON-B deserializes JSON to Product]
  B --> C["Bean Validation checks @Valid constraints"]
  C -->|invalid| D[400 Bad Request - your code never runs]
  C -->|valid| E[CDI ProductService runs in @Transactional]
  E --> F[JPA persists the row]
  F --> G[JSON-B serializes the result back to JSON]
```

JSON-B turns bytes into an object, Bean Validation guards the gate, your CDI service (from
[Phase 3](03-cdi-dependency-injection.md)) does the work inside a `@Transactional` (from
[Phase 6](06-transactions-with-jta.md)), JPA (from [Phase 5](05-jakarta-persistence.md)) writes the row,
and JSON-B serializes the answer. Each spec owns one slice; together they're the spine of a Jakarta API.

⚠️ **Validate at the boundary - don't trust client input.** It is tempting to assume "my front-end already
checks the form, so the server can relax." It can't. Anyone can send a raw HTTP request that skips your
front-end entirely: a curl command, a buggy mobile client, a malicious script. The server is the *only*
place you actually control, so the constraints on your `Product` plus a `@Valid` at the edge are not
belt-and-suspenders - they're the belt. Bad data that slips past validation becomes corrupt rows, and
corrupt rows outlive the bug that created them.

## Recap

1. **Bean Validation puts rules on the data.** Annotate fields with `@NotBlank`, `@NotNull`, `@Size`,
   `@Positive`, `@Min`/`@Max`, `@Email` - the rule lives next to the field and travels with the object.
   It's the same spec Spring uses, down to the package names.
2. **`@Valid` enforces them in JAX-RS.** Put `@Valid` on the body parameter; the container validates the
   deserialized object before your method runs and returns a 400 with the violations if it fails - your
   code never sees bad input.
3. **Validation isn't only for HTTP.** `@Valid` works on CDI/EJB method parameters too (method-level
   validation), and you can inject a `Validator` to validate programmatically when you need full control.
4. **JSON-B is the standard object↔JSON mapper.** JAX-RS uses it automatically; `@JsonbProperty` renames a
   key, `@JsonbTransient` hides a field, and date/number format annotations shape the output. JSON-P sits
   below it for low-level streaming when you don't have a class to bind to.
5. **Custom constraints are annotation + `ConstraintValidator`.** Define your own rule (like a SKU format
   check) and it plugs into `@Valid` exactly like a built-in. ⚠️ Always validate at the boundary - the
   client can't be trusted, and the server is the only gate you control.

## Quick check

Make sure the validation and binding ideas stuck:

```quiz
[
  {
    "q": "You annotated Product.name with @NotBlank, but blank names still reach your service and crash it. What did you most likely forget?",
    "choices": [
      "Adding @Valid to the Product parameter on the resource method, which is the switch that tells the container to actually enforce the constraints",
      "Importing the jakarta.validation package into the Product class",
      "Annotating the service with @Transactional so validation can roll back",
      "Setting @Produces(APPLICATION_JSON) on the method"
    ],
    "answer": 0,
    "explain": "Constraint annotations on a field are just metadata until something triggers them. In JAX-RS, @Valid on the body parameter is that trigger - it tells the container to validate the deserialized object before your method runs. Without @Valid, the rules are inert and bad input passes straight through."
  },
  {
    "q": "What is the role of JSON-B (Jakarta JSON Binding) in a JAX-RS request?",
    "choices": [
      "It serializes your return values to JSON and deserializes incoming JSON into Java objects - the standard object-to-JSON mapper, the role Jackson plays in Spring",
      "It validates incoming objects against their constraint annotations",
      "It manages the database transaction around the request",
      "It routes the request to the correct resource method based on the URL"
    ],
    "answer": 0,
    "explain": "JSON-B is the binding spec: object↔JSON. JAX-RS uses it automatically to turn the request body into a Java object and your return value back into JSON. Validation is Bean Validation's job (a separate spec), transactions are JTA's, and routing is JAX-RS's @Path matching."
  },
  {
    "q": "You need a reusable custom rule that checks a SKU matches your catalog's pattern, usable on any field via @Valid. What do you build?",
    "choices": [
      "A custom annotation paired with a class implementing ConstraintValidator, wired together with @Constraint(validatedBy = ...)",
      "A subclass of Product that overrides a validate() method",
      "An ExceptionMapper that inspects the SKU and throws on a bad value",
      "A @JsonbProperty annotation with a regex argument"
    ],
    "answer": 0,
    "explain": "A custom constraint is an annotation (marked @Constraint and pointed at a validator) plus a ConstraintValidator implementation whose isValid does the check. Once defined, it plugs into @Valid like any built-in constraint. JSON-B annotations control JSON shape, not validation, and ExceptionMapper handles errors, not the rule itself."
  }
]
```


---

# Enterprise Beans & Messaging

By now you've met CDI beans (Phase 3) and watched the container build and wire your objects for you.
This phase introduces a second family of container-managed components - **enterprise beans (EJB)** - and
then steps outside the single application entirely to the question every real system eventually hits:
*how do my services hand work to each other when nobody's waiting for an answer?* That second half is
**messaging** - the part that keeps enterprise systems from collapsing under their own coupling.

Here's the mental model to carry through both halves: so far every call in your app has been *synchronous*
 - someone calls a method, blocks, and gets a result. This phase is about everything that happens **on a
schedule**, **in the background**, or **between services that never block on each other**. Same platform,
new dimension: time.

## Enterprise Beans (EJB), plainly

📝 An **enterprise bean (EJB)** is a container-managed business component - a class where the application
server takes over the heavy lifting: transactions, pooling, concurrency, security, lifecycle. EJBs have a
*reputation*. In the Java EE 5 era they were genuinely painful - home interfaces, remote interfaces,
mountains of XML, one trivial bean spread across four files. That EJB is dead. The modern one is a single
annotated class, and it's worth knowing because it still does real work for you.

📝 The one you'll meet constantly is the **`@Stateless` session bean** - a *pooled* bean that's
**transactional by default**. "Pooled" means the container keeps a small stable of instances and hands one
to each caller, then takes it back; you never see two callers sharing the same instance at the same time.
"Transactional by default" means every public method runs inside a database transaction automatically - 
no annotation required.

```java
import jakarta.ejb.Stateless;
import jakarta.persistence.EntityManager;
import jakarta.persistence.PersistenceContext;

@Stateless
public class ProductService {

    @PersistenceContext
    private EntityManager em;

    public void create(Product product) {
        em.persist(product); // runs inside a container-managed transaction, automatically
    }

    public Product findBySku(String sku) {
        return em.createQuery("SELECT p FROM Product p WHERE p.sku = :sku", Product.class)
                 .setParameter("sku", sku)
                 .getSingleResult();
    }
}
```
*What just happened:* `@Stateless` turns `ProductService` into a pooled, transactional bean. When some
caller invokes `create(...)`, the container grabs a free instance from the pool, opens a transaction,
runs the method, commits (or rolls back on exception), and returns the instance to the pool. You wrote
`em.persist(product)` and nothing about transactions - the container supplied all of that. One instance
is never used by two callers concurrently, so you don't synchronize anything.

📝 The three session-bean flavors, so you can read any enterprise codebase:

| Annotation | Lifecycle | Shared? | Reach for it when… |
|------------|-----------|---------|--------------------|
| `@Stateless` | Pooled; no per-client state kept between calls | One caller at a time (pool) | Almost always - stateless services, the default |
| `@Stateful` | One instance dedicated to one client across calls | Tied to a single client | A multi-step conversation holds state (a wizard, a cart) |
| `@Singleton` | Exactly one instance for the whole app | Shared by everyone | App-wide shared state or startup work (`@Startup`) |

⚠️ `@Stateful` is the one people misuse. It pins an instance to a client and keeps it alive between calls,
which means memory and cleanup concerns - and in a clustered deployment, the state has to follow the
client around. Most "I need stateful" turns out to be "I need to store this somewhere," and the plain
answer is usually a database or a session, not a stateful bean. Reach for `@Stateless` by default.

## EJB vs CDI bean (the plain take)

You may be squinting at `@Stateless ProductService` thinking it looks an awful lot like the
`@ApplicationScoped ProductService` from Phase 3. Good instinct - they overlap heavily, and choosing
between them trips up newcomers.

⚠️ **Modern Jakarta EE largely prefers a CDI bean + `@Transactional` over an EJB.** A plain
`@ApplicationScoped` class with `@Transactional` on its methods gives you the same declarative
transactions through CDI's interceptor machinery, using one consistent programming model for your whole
app - no separate EJB lifecycle to reason about.

```java
import jakarta.enterprise.context.ApplicationScoped;
import jakarta.transaction.Transactional;
import jakarta.persistence.EntityManager;
import jakarta.persistence.PersistenceContext;

@ApplicationScoped
public class ProductService {

    @PersistenceContext
    private EntityManager em;

    @Transactional                       // CDI interceptor wraps this call in a transaction
    public void create(Product product) {
        em.persist(product);
    }
}
```
*What just happened:* this is the Phase 3 CDI bean with one addition - `@Transactional` from Jakarta
Transactions. The interceptor wraps `create(...)` in a database transaction exactly like the `@Stateless`
version did, but the bean is an ordinary CDI bean: same `@Inject`, same scopes, same testing seam. No EJB
container behavior involved. For brand-new code, this is the path most teams take today.

So why learn EJB at all? Two reasons. First, **you will read it** - EJBs are everywhere in existing
enterprise code, and you can't maintain what you can't recognize. Second, `@Stateless` still hands you a
few things for free that you'd otherwise wire up: **instance pooling**, **declarative transactions
without annotating each method**, and - because of the one-caller-at-a-time pooling - **effective
thread-safety** inside the bean.

💡 The rule of thumb: **know EJBs to read enterprise code; reach for CDI + `@Transactional` in new code.**
They're not enemies - under the hood a modern EJB *is* a CDI-managed bean with extra container services
bolted on. Same family, different amount of magic.

## Scheduling: background jobs without a cron daemon

Real systems have chores: expire stale carts at midnight, recompute a sales rollup hourly, email a digest
every Monday. You *could* stand up an external scheduler (cron, a job server) - but Jakarta EE has a timer
service built in, and the `@Schedule` annotation drives it declaratively.

```java
import jakarta.ejb.Schedule;
import jakarta.ejb.Singleton;

@Singleton
public class ProductMaintenance {

    @Schedule(hour = "3", minute = "0", persistent = false)
    public void purgeDiscontinuedProducts() {
        // runs every night at 03:00, scheduled and invoked by the container
        // ... delete products flagged discontinued, in a container-managed transaction
    }
}
```
*What just happened:* `@Schedule` registers a timer with the container's timer service. The `hour`/`minute`
attributes are a cron-like calendar expression, so this fires at 03:00 every day with no external
scheduler involved - the container wakes the bean and calls the method for you, inside a transaction. We
put it on a `@Singleton` because there should be exactly one scheduler instance for the app, not a pool of
them all firing at once.

💡 `persistent = false` means "if the server is down at 03:00, skip that run - don't replay it later."
The default (`true`) stores missed timers and fires them on restart, which is what you want for jobs that
*must* eventually run (billing) and emphatically not what you want for jobs that are only useful *now*
(a cache refresh). Pick deliberately.

## Asynchronous methods: fire-and-forget

Sometimes a method does slow work the caller shouldn't have to wait on - generating a report, calling a
sluggish third-party API, sending a batch of emails. `@Asynchronous` tells the container to run the method
on a separate thread and return control to the caller immediately.

```java
import jakarta.ejb.Asynchronous;
import jakarta.ejb.Stateless;
import java.util.concurrent.Future;
import jakarta.ejb.AsyncResult;

@Stateless
public class ProductReportService {

    @Asynchronous
    public void reindexCatalog() {
        // slow work runs on a container thread; caller doesn't block
    }

    @Asynchronous
    public Future<Integer> countAndExport() {
        int rows = /* slow export */ 0;
        return new AsyncResult<>(rows); // caller can poll/await this later
    }
}
```
*What just happened:* calling `reindexCatalog()` returns instantly - the container schedules the body on
one of its managed threads and the caller moves on. The second method returns a `Future`, so a caller who
*does* eventually want the result can hold the handle and call `.get()` when ready. Either way, the slow
work is offloaded.

⚠️ Async changes the rules around context. The async method runs on a **different thread**, so it gets a
**new transaction** - it does not join the caller's. If the caller's transaction later rolls back, the
async work has already committed independently; they're decoupled by design. Request-scoped data and
security context don't automatically flow across the thread boundary either. Use `@Asynchronous` for work
that's genuinely independent of the caller's transaction - not as a way to "speed up" something that needs
to be part of the same atomic unit.

## Messaging: how services stay loosely coupled

Everything so far has lived inside one application. Now zoom out. You have an order service and an
inventory service and an email service, and an order needs to touch all three. If the order service calls
each one directly and waits, you've built a chain that's only as available as its weakest link - email
provider hiccups, the whole order stalls.

📝 **Messaging** breaks that chain. Instead of calling a service, a **producer** drops a **message** onto a
**queue** (or **topic**), and a **consumer** picks it up and processes it **asynchronously**, whenever it's
ready. The producer doesn't know or care who consumes it, or when. That's *decoupling*: the two sides are
connected only by the shape of the message, not by being up at the same instant.

This is the same machinery covered conceptually in
[/guides/webhooks-and-message-queues](/guides/webhooks-and-message-queues) - a message queue as a to-do
list between your own services, absorbing spikes and surviving outages. **Jakarta Messaging (JMS)** is the
standard Jakarta EE API for that pattern, and the application server gives you a message broker to talk to.

The producer side is small - inject a `JMSContext` and send:

```java
import jakarta.ejb.Stateless;
import jakarta.inject.Inject;
import jakarta.jms.JMSContext;
import jakarta.jms.Queue;
import jakarta.annotation.Resource;

@Stateless
public class ProductEventPublisher {

    @Inject
    private JMSContext jms;

    @Resource(lookup = "java:/jms/queue/ProductCreated")
    private Queue productCreatedQueue;

    public void announceNewProduct(Product product) {
        jms.createProducer().send(productCreatedQueue, product.sku());
        // returns immediately; the message now waits in the queue for a consumer
    }
}
```
*What just happened:* `announceNewProduct` puts a message (the new product's SKU) onto the
`ProductCreated` queue and returns. There's no consumer in sight, and that's the point - whoever cares
about new products reads from this queue on their own schedule. The broker holds the message safely until
someone picks it up, so a down consumer means a backlog, not a lost event.

The consumer side is a **message-driven bean (MDB)** - a bean the container invokes automatically whenever
a message arrives. You don't poll; you just declare what queue you listen to:

```java
import jakarta.ejb.MessageDriven;
import jakarta.ejb.ActivationConfigProperty;
import jakarta.jms.Message;
import jakarta.jms.MessageListener;
import jakarta.jms.TextMessage;

@MessageDriven(activationConfig = {
    @ActivationConfigProperty(propertyName = "destinationLookup",
                              propertyValue = "java:/jms/queue/ProductCreated")
})
public class ProductCreatedListener implements MessageListener {

    @Override
    public void onMessage(Message message) {
        // container calls this once per message, on its own thread
        // ... e.g. warm a cache or reindex search for the new product
    }
}
```
*What just happened:* `@MessageDriven` ties this bean to the `ProductCreated` queue. The container watches
the queue and calls `onMessage(...)` once for every message that lands - no loop, no polling code, no
thread management on your part. Each invocation runs in its own transaction, so a failure can put the
message back for a retry instead of dropping it.

💡 This is the heart of why enterprise systems use messaging: **loose coupling buys you resilience.** The
order service stays fast and available even when a downstream consumer is slow or down, spikes get
absorbed by the queue instead of toppling a service, and you can add new consumers (analytics, audit) to
the same event without touching the producer. The trade-offs - duplicate deliveries, retries, ordering,
dead-letter queues - are exactly the gotchas covered in
[/guides/webhooks-and-message-queues](/guides/webhooks-and-message-queues), and they apply here too.

## Recap

1. **Enterprise beans (EJB)** are container-managed business components. The modern EJB is a single
   annotated class, not the XML nightmare of old. The workhorse is `@Stateless` - a *pooled*, *transactional
   by default* session bean. `@Stateful` pins one instance to one client; `@Singleton` is one instance for
   the whole app.
2. **EJB vs CDI:** new code usually prefers a CDI bean (`@ApplicationScoped`) plus `@Transactional` - one
   consistent model. Learn EJBs to *read* enterprise code; `@Stateless` still hands you pooling,
   declarative transactions, and one-caller-at-a-time thread-safety for free.
3. **Scheduling:** `@Schedule` drives the built-in timer service with cron-like calendar expressions - no
   external scheduler. ⚠️ `persistent` decides whether missed runs replay on restart; choose deliberately.
4. **Asynchronous methods:** `@Asynchronous` offloads slow work to a container thread and returns
   immediately (optionally a `Future`). ⚠️ It runs in a *new* transaction and context doesn't flow across
   the thread - use it only for work independent of the caller.
5. **Messaging (Jakarta Messaging / JMS):** a producer sends a message to a queue; a message-driven bean
   (`@MessageDriven`) consumes it asynchronously. This decouples services - the producer never waits on the
   consumer - which is how enterprise systems stay loosely coupled and resilient.

With background, scheduled, and cross-service work covered, the remaining gap is keeping all of it safe.
Next phase locks the doors: authentication and authorization with Jakarta Security.

## Quick check

Test yourself on the ideas that have to stick from this phase:

```quiz
[
  {
    "q": "What does @Stateless give you that a plain Java class wouldn't?",
    "choices": [
      "Instance pooling, declarative transactions by default, and one-caller-at-a-time thread-safety",
      "Automatic REST endpoints for every public method",
      "A persistent timer that fires the methods on a schedule",
      "A new database connection created per method call"
    ],
    "answer": 0,
    "explain": "A @Stateless session bean is pooled (one caller at a time per instance, so it's effectively thread-safe inside), and every public method runs inside a container-managed transaction by default - none of which you wrote yourself."
  },
  {
    "q": "For brand-new code, which is the approach modern Jakarta EE generally prefers for transactional business logic?",
    "choices": [
      "A CDI bean (@ApplicationScoped) with @Transactional on its methods",
      "An EJB with home and remote interfaces plus deployment XML",
      "A @Stateful session bean shared across all clients",
      "A @MessageDriven bean that calls itself"
    ],
    "answer": 0,
    "explain": "New code usually reaches for a plain CDI bean plus @Transactional - one consistent programming model. EJBs are worth knowing mainly because you'll read them in existing enterprise code."
  },
  {
    "q": "In Jakarta Messaging, why does sending a message to a queue keep services loosely coupled?",
    "choices": [
      "The producer drops the message and returns; a consumer processes it asynchronously whenever it's ready, so neither blocks on the other",
      "The producer waits for the consumer to finish before returning a result",
      "The queue forces the producer and consumer to share the same transaction",
      "Each message is delivered only if the consumer is online at that exact moment"
    ],
    "answer": 0,
    "explain": "A producer sends to the queue and moves on; the broker holds the message until a consumer (a message-driven bean) picks it up. The two sides are connected only by the message shape, not by being up at the same instant - that decoupling is what buys resilience."
  }
]
```


---

# Jakarta Security

Your `Product` API from [Phase 4](04-jax-rs-rest-apis.md) works beautifully - and right now, anyone on the internet can read it, write to it, and delete from it. That's fine for a demo on `localhost`. It is a catastrophe in production. This phase bolts a door onto the front of that API.

Security has a reputation for being a swamp, and Jakarta's history doesn't help: for years there were three competing, overlapping ways to do it (servlet security, JAAS, vendor-specific config) and tutorials that handed you XML you couldn't read. We're going to ignore all of that. Modern Jakarta EE has *one* coherent story - the **Jakarta Security API** - and it's built on two questions that, once you separate them cleanly, make everything else click into place.

Here are the two questions. Burn them into your memory before we touch code.

> **Authentication: who are you?**
> **Authorization: are you allowed to do this?**

Get those two straight and the rest of this phase is just learning which Jakarta annotation answers which one.

## Authentication vs authorization - two different questions

These words sound alike, get abbreviated to the nearly-identical *authN* and *authZ*, and are confused constantly - including in shipping production code that conflates them and opens a hole.

📝 **Authentication (authN) = "who are you?"** It's the login step: you prove your identity with a password, an API key, a token. The output is a verified identity, or a rejection. **Authorization (authZ) = "are you allowed to do this?"** It runs *after* authentication, on a now-known user, and checks whether that user may perform *this specific action* - usually via **roles** like `ADMIN` or `USER`.

A worked example makes the split obvious: logging into your bank's app is authentication - the app now knows it's you. Being told "you can view your own account but not transfer from someone else's" is authorization. You stay fully authenticated the whole time; you're just not authorized for that one action. Mix these up - confirm someone is logged in but never check *what* they're allowed to do - and any logged-in user can do anything. That is one of the most common real-world security bugs.

In Jakarta EE these two steps are handled by two different pieces of machinery, in order:

```mermaid
flowchart LR
  Req[HTTP request to /api/products] --> AM[Authentication mechanism]
  AM --> IS[Identity store validates]
  IS --> ID[Identity established: user + roles]
  ID --> AZ[Authorization check: @RolesAllowed]
  AZ -->|allowed| Res[ProductResource method runs]
  AM -.no credentials.-> Deny[401 Unauthorized]
  AZ -.wrong role.-> Forbid[403 Forbidden]
```

*What just happened:* A request first meets an **authentication mechanism**, which extracts credentials and asks an **identity store** to validate them. If that succeeds, the container records *who* you are and *which roles* you hold - that's the established identity. Only then does the **authorization** check run, comparing your roles against what the resource demands. Two failure exits, and they mean different things: no valid credentials gives **401 Unauthorized** ("I don't know who you are"); valid credentials but insufficient role gives **403 Forbidden** ("I know who you are, and the answer is no"). This authN-then-authZ ordering is foundational enough to have its own guide: [Authentication vs Authorization](/guides/auth-vs-authz).

## Authentication mechanisms - proving who you are

So how does the container *get* your credentials off the wire? That's the job of an **authentication mechanism**.

📝 In modern Jakarta Security, an `HttpAuthenticationMechanism` is the component that inspects an incoming request, pulls out whatever credentials it carries, and kicks off validation. You rarely write one by hand - the spec ships ready-made ones you switch on with a single annotation. The container then runs the chosen mechanism *before* your resource method, every request.

The built-in choices map to how clients actually authenticate:

- **`@BasicAuthenticationMechanismDefinition`** - HTTP Basic auth: the client sends `username:password` (base64-encoded) in an `Authorization` header. Simple, common for machine-to-machine APIs.
- **`@FormAuthenticationMechanismDefinition`** - a login form and a session cookie afterward. The right fit for browser apps with human users.
- **A custom mechanism** - implement `HttpAuthenticationMechanism` yourself when you need something the built-ins don't cover (this is how JWT support is often wired in, more on that later).

Turning on Basic auth for our `Product` API is a single annotation, placed on any CDI bean (your `Application` class is a natural home):

```java
import jakarta.security.enterprise.authentication.mechanism.http.BasicAuthenticationMechanismDefinition;
import jakarta.ws.rs.ApplicationPath;
import jakarta.ws.rs.core.Application;

@BasicAuthenticationMechanismDefinition(realmName = "product-api")
@ApplicationPath("/api")
public class RestConfig extends Application {
    // the annotation does the work; the class body stays empty
}
```

*What just happened:* `@BasicAuthenticationMechanismDefinition` told the container "for this application, authenticate requests using HTTP Basic." Now, before any `ProductResource` method runs, the container reads the `Authorization: Basic ...` header, decodes the username and password, and hands them off for validation. You didn't parse a header or decode base64 - you *declared* the mechanism and the container supplies the plumbing. Notice this annotation only answers *how credentials arrive*; it says nothing about *where the real users live*. That's the next, separate piece.

## Identity stores - where users actually live

A mechanism extracts a username and password. Something still has to answer: *is this a real user, is this their real password, and what roles do they have?* That something is an **identity store**.

📝 An `IdentityStore` is the source of truth for credentials and roles. The mechanism passes it the submitted credentials; the store looks the user up, verifies the password, and returns the user's roles (or "invalid"). Jakarta gives you declarative built-ins for the common backends:

- **`@DatabaseIdentityStoreDefinition`** - users and roles live in your database; you give it two SQL queries (one for the password hash, one for the roles).
- **`@LdapIdentityStoreDefinition`** - users live in an LDAP/Active Directory server.
- **A custom `IdentityStore`** - implement the interface when your users live somewhere else entirely.

Here's a database-backed store for our app, sitting alongside the mechanism:

```java
import jakarta.security.enterprise.identitystore.DatabaseIdentityStoreDefinition;
import jakarta.security.enterprise.identitystore.Pbkdf2PasswordHash;

@DatabaseIdentityStoreDefinition(
    dataSourceLookup = "java:app/jdbc/ProductDB",
    callerQuery   = "SELECT password_hash FROM users WHERE username = ?",
    groupsQuery   = "SELECT role FROM user_roles WHERE username = ?",
    hashAlgorithm = Pbkdf2PasswordHash.class
)
public class AppIdentityStore { }
```

*What just happened:* `callerQuery` fetches the stored password **hash** for the submitted username; the container hashes the submitted password the same way and compares - your raw password is never matched against plaintext. `groupsQuery` returns that user's roles (the spec calls them "groups"), which become the basis for authorization later. `hashAlgorithm = Pbkdf2PasswordHash.class` tells the store the passwords are hashed with PBKDF2, so it knows how to verify them.

⚠️ **Store password hashes, never plaintext.** That `password_hash` column must hold a salted, slow hash (PBKDF2, as above, or BCrypt) - *never* the raw password. If your database leaks and the passwords are plaintext, every account is instantly compromised everywhere those people reused that password. Hash them, and a leak yields useless gibberish. *Why* a slow salted hash (and not encryption, and not a fast hash like MD5) is its own rich topic - see [How Passwords Are Stored](/guides/how-passwords-are-stored). This is not a corner to cut.

## Authorization with roles - deciding what you can do

Identity established, roles in hand. Now: *should this user be allowed to do this?* This is where the two failure modes finally diverge in code - and where you protect the dangerous parts of your `Product` API.

📝 The standard, declarative tool is `@RolesAllowed`. You put it on a resource method (or an enterprise bean method from [Phase 8](08-enterprise-beans-and-messaging.md)) and name the roles permitted to call it. Its companions:

- **`@RolesAllowed("ADMIN")`** - only callers with the `ADMIN` role may invoke this.
- **`@PermitAll`** - anyone may call it, authenticated or not (good for public reads).
- **`@DenyAll`** - nobody may call it (rare, but explicit).

The natural shape for our API: let anyone *read* products, but require `ADMIN` to *create* or *delete* them.

```java
import jakarta.annotation.security.RolesAllowed;
import jakarta.annotation.security.PermitAll;
import jakarta.ws.rs.*;
import jakarta.ws.rs.core.MediaType;
import jakarta.ws.rs.core.Response;
import java.util.List;

@Path("/products")
public class ProductResource {

    @Inject
    private ProductService service;

    @GET
    @PermitAll                                  // reading is public
    @Produces(MediaType.APPLICATION_JSON)
    public List<Product> listProducts() {
        return service.findAll();
    }

    @POST
    @RolesAllowed("ADMIN")                       // creating requires ADMIN
    @Consumes(MediaType.APPLICATION_JSON)
    public Response createProduct(Product product) {
        Product saved = service.create(product);
        return Response.status(Response.Status.CREATED).entity(saved).build();
    }
}
```

*What just happened:* `@PermitAll` on `listProducts` keeps the catalog open to everyone - no credentials needed. `@RolesAllowed("ADMIN")` on `createProduct` tells the container to run the authorization check *after* authentication: only a caller whose identity store returned the `ADMIN` role gets through. A logged-in non-admin reaches `createProduct` already authenticated, fails the role check, and is rejected - without your method body ever running. You wrote zero `if (user.hasRole(...))` plumbing; the annotation *is* the rule.

What a rejected write looks like - a valid but non-admin user trying to create a product:

```http
POST /api/products HTTP/1.1
Host: api.example.com
Authorization: Basic dXNlcjpwYXNzd29yZA==

{ "name": "Laptop Stand", "price": 39.95, "sku": "STD-LAP-04" }
```

```console
HTTP/1.1 403 Forbidden
```

*What just happened:* The credentials were valid (so it's **not** 401 - the server knows who this is), but the user lacks the `ADMIN` role, so authorization denied the request with **403 Forbidden**. That status is the whole authN/authZ distinction expressed in one number: *I know exactly who you are, and you still can't do this.*

When a decision depends on the *data*, not just a fixed role, declarative annotations aren't enough - and Jakarta gives you a programmatic escape hatch. Inject `SecurityContext` and ask it directly:

```java
import jakarta.security.enterprise.SecurityContext;

@DELETE
@Path("/{id}")
@RolesAllowed({"ADMIN", "MANAGER"})
public Response deleteProduct(@PathParam("id") Long id) {
    if (!securityContext.isCallerInRole("ADMIN") && service.isFlagship(id)) {
        return Response.status(Response.Status.FORBIDDEN).build();   // only ADMIN may delete flagship items
    }
    service.delete(id);
    return Response.noContent().build();
}
```

*What just happened:* `@RolesAllowed` did the coarse gate (admins and managers only), then `securityContext.isCallerInRole("ADMIN")` made a finer, *runtime* decision the annotation couldn't express: managers may delete ordinary products but not flagship ones. 💡 Reach for `SecurityContext` only when the rule genuinely depends on the request's data - for fixed role rules, the declarative `@RolesAllowed` is clearer and harder to get wrong.

## Stateless APIs & JWT - the overview, and the one rule

The Basic-auth-and-session model assumes the server *remembers* you between requests. For a REST API serving mobile apps and other services, that's often the wrong shape - you'd rather each request carry its own proof and the server keep no session memory at all.

📝 **A stateless API carries identity on every request instead of relying on a server-side session.** The dominant approach is the **JWT (JSON Web Token)**: at login the server hands the client a *signed* token encoding who they are, their roles, and when it expires. The client sends it back in an `Authorization: Bearer <token>` header on every subsequent request. A mechanism validates the signature and expiry and, if it checks out, establishes the identity - *with no session lookup*. In Jakarta-land this is standardized by **MicroProfile JWT**, which we meet in [Phase 10](10-microprofile-and-where-next.md); the beauty is that once the token is validated, your `@RolesAllowed` annotations work *exactly the same* - the roles just come from the token instead of an identity store.

```http
GET /api/products HTTP/1.1
Host: api.example.com
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...
```

*What just happened:* The client attaches its signed token in the `Authorization` header. A JWT mechanism reads it, verifies the signature against the issuer's key, checks it hasn't expired, and populates the caller's identity and roles - so the downstream authorization rules do their job without the server remembering anything. Any instance of your service can validate the token independently, which is exactly why this scales horizontally.

We are deliberately *not* hand-rolling a JWT implementation here, and you should be wary of any tutorial that casually does. ⚠️ The footguns are real and unforgiving:

- **Token storage on the client** - `localStorage` is exposed to XSS; `HttpOnly` cookies dodge that but invite CSRF. There's no free lunch; choose deliberately.
- **Expiry and revocation** - a stateless token can't be "logged out" server-side without extra machinery (a denylist, or short-lived tokens plus refresh tokens). Always set a sane expiry.
- **Transport** - a Bearer token is a password in plain text. ⚠️ Over plain HTTP, anyone on the network reads it and impersonates the user. **Always HTTPS in production**, no exceptions - see [HTTPS and TLS](/guides/https-and-tls).
- **Never roll your own crypto or token parsing** - this is the cardinal sin. Signature verification has subtle, exploited-in-the-wild failure modes (the infamous `alg: none` attack, among others).

💡 The throughline for this whole phase: **security is the worst possible place to improvise.** Lean on the container's standard mechanisms - `HttpAuthenticationMechanism`, declarative identity stores, `@RolesAllowed`, MicroProfile JWT. They encode years of patched vulnerabilities and hard-won lessons your clever shortcut hasn't survived. The most secure code you'll write in this phase is the code you *didn't* write because the spec already had it.

## Recap

1. **AuthN and authZ are two different questions.** Authentication is "who are you?" (the login step); authorization is "are you allowed to do this?" (roles), and it runs second on a now-known user. No credentials → **401**; valid credentials but wrong role → **403**. Conflating them is a classic hole - see [Authentication vs Authorization](/guides/auth-vs-authz).
2. **An authentication mechanism gets credentials off the wire.** `@BasicAuthenticationMechanismDefinition`, `@FormAuthenticationMechanismDefinition`, or a custom `HttpAuthenticationMechanism` - you *declare* one and the container runs it before your resource. It says *how* credentials arrive, not *where* users live.
3. **An identity store validates credentials and supplies roles.** `@DatabaseIdentityStoreDefinition` / `@LdapIdentityStoreDefinition` / a custom `IdentityStore`. ⚠️ Store salted, slow password *hashes* (PBKDF2/BCrypt), never plaintext - see [How Passwords Are Stored](/guides/how-passwords-are-stored).
4. **Authorize with `@RolesAllowed`.** Put it on resource or EJB methods; `@PermitAll` opens a method to everyone, `@DenyAll` closes it. For data-dependent rules, inject `SecurityContext` and call `isCallerInRole(...)` - but prefer the declarative annotation when the rule is fixed.
5. **Stateless APIs use JWT.** A signed `Authorization: Bearer ...` token carries identity and roles per request, no session; MicroProfile JWT (Phase 10) standardizes it and your `@RolesAllowed` keeps working. ⚠️ Mind token storage, expiry/revocation, always use HTTPS ([HTTPS and TLS](/guides/https-and-tls)), and never roll your own crypto.
6. **Don't improvise security.** Lean on the container's battle-tested standard mechanisms; the safest code is the code the spec already wrote and patched for you.

## Quick check

Make sure the authN/authZ split - and which Jakarta piece answers which question - actually stuck:

```quiz
[
  {
    "q": "A user with valid credentials but only the USER role calls a @RolesAllowed(\"ADMIN\") endpoint. What status should they get, and why?",
    "choices": [
      "403 Forbidden - the server knows who they are (authentication passed) but they lack the required role (authorization failed)",
      "401 Unauthorized - they failed to prove their identity",
      "200 OK - being logged in is enough to call any endpoint",
      "500 Internal Server Error - the role mismatch crashes the handler"
    ],
    "answer": 0,
    "explain": "Authentication succeeded, so it's not 401 - the server knows who they are. Authorization then failed because the caller lacks the ADMIN role, which is exactly what 403 Forbidden means: 'I know who you are, and the answer is no.' 401 means 'I don't know who you are.'"
  },
  {
    "q": "In modern Jakarta Security, what is the job of an identity store (e.g. @DatabaseIdentityStoreDefinition)?",
    "choices": [
      "It validates the submitted credentials against a backend and returns the user's roles",
      "It extracts the Authorization header off the incoming HTTP request",
      "It decides which roles are allowed to call a given resource method",
      "It encrypts the response body before sending it to the client"
    ],
    "answer": 0,
    "explain": "The authentication mechanism extracts credentials from the request; the identity store is the source of truth that validates them (against a database, LDAP, etc.) and returns the user's roles. Authorization - who may call what - is handled separately by @RolesAllowed."
  },
  {
    "q": "Which statement about securing a stateless JWT-based API is correct?",
    "choices": [
      "Use a vetted library/standard (like MicroProfile JWT), always serve over HTTPS, and set a token expiry",
      "Tokens make HTTPS unnecessary because they are already signed",
      "You should write your own signature-verification code so you fully control it",
      "A signed JWT can always be instantly revoked server-side with no extra machinery"
    ],
    "answer": 0,
    "explain": "A Bearer token is a password in plaintext, so HTTPS is mandatory - signing proves integrity, not confidentiality. Never roll your own crypto (the alg:none attack is real); lean on a vetted standard like MicroProfile JWT. And a stateless token can't be revoked without extra machinery (denylist or short-lived tokens + refresh), so always set an expiry."
  }
]
```


---

# MicroProfile & Where to Go Next

Stop for a second and look at what you can do now. You can stand up a real Jakarta EE service from nothing: wire your objects together with **CDI**, expose them over HTTP with **JAX-RS**, persist data through **Jakarta Persistence**, keep writes correct with **JTA transactions**, guard your inputs with **Validation**, and lock the doors with **Jakarta Security**. More than the annotations, you understand the *model underneath* - specifications you write against, application servers that implement them, and the freedom to swap the engine without rewriting your code.

That's not a small thing. A lot of people who use enterprise Java never quite see that shape. You do. This last phase isn't more specs to memorize - it's the map of where you go from here and the clear-eyed version of what your new skill is worth.

## MicroProfile - the cloud-native companion

📝 Here's the gap. Classic Jakarta EE was designed in an era of big application servers running in a data center, and it didn't have first-class answers for the things modern cloud deployments need: pulling configuration from the environment, telling Kubernetes whether you're alive, exposing metrics, surviving a flaky downstream service. **MicroProfile** is the open-standard project that fills exactly that gap, built by the same community and designed to sit right alongside the Jakarta EE specs you already know.

It's a set of small, focused specifications:

- **Config** - read settings (database URLs, feature flags, secrets) from the environment instead of hard-coding them, so the same build runs in dev, staging, and prod.
- **Health** - expose readiness and liveness endpoints so Kubernetes knows when to send you traffic and when to restart you.
- **Metrics** - publish counters and timers a monitoring system can scrape.
- **OpenAPI** - generate live, accurate API documentation straight from your JAX-RS resources.
- **Fault Tolerance** - add retries, timeouts, and circuit breakers with annotations, so one slow dependency doesn't take you down.
- **JWT** - authenticate callers with signed tokens, the standard currency of microservices.

💡 Put it together and the picture is clean: **Jakarta EE gives you the application; MicroProfile gives you the cloud-native operability - and both are open standards, not one vendor's framework.** That combination is a genuinely modern microservices stack.

```mermaid
flowchart TD
  Core[Your skills: CDI · JAX-RS · JPA] --> MP[MicroProfile]
  MP --> Cfg[Config]
  MP --> Hlth[Health]
  MP --> Met[Metrics]
  MP --> API[OpenAPI]
  MP --> FT[Fault Tolerance]
  MP --> JWT[JWT]
```

## The runtimes - Jakarta EE's modern face

For years the knock on enterprise Java was startup time and memory: heavyweight servers that took a minute to boot and a gigabyte to idle. That era is over. 📝 A new generation of runtimes implements Jakarta EE *and* MicroProfile while running as fast, small, self-contained applications:

- **Quarkus** - built for the cloud and for fast startup, with a focus on containers and even native compilation.
- **Helidon** - Oracle's lightweight take, designed for microservices from the ground up.
- **Open Liberty** - IBM's modular server, light and composable.
- **Payara Micro** and **WildFly** - the established servers you met earlier, also runnable in slim, modern modes.

These are the standards-based answer to Spring Boot: write against the same Jakarta EE and MicroProfile annotations, then run a single, quick-starting executable that's at home in a container. **Quarkus** in particular has become a strong cloud-native choice (and has its own guide coming). The headline: the code you already know how to write now runs in exactly the lightweight, microservice-shaped way the rest of the industry expects.

## Where this fits in a career

Let's look plainly at value. Jakarta EE skills are not niche - they run a huge share of enterprise backends at banks, insurers, governments, and large companies, the kind of systems that stay in production for a decade. And because you learned the *standard*, those skills travel: the same CDI and JAX-RS knowledge works across WildFly, Payara, Open Liberty, Quarkus, and Helidon. You're not locked to one vendor.

There's a bigger payoff, too. Spring borrowed many of these ideas - dependency injection, JPA, declarative transactions - so reading Spring code now feels familiar instead of foreign. 💡 By learning the standard, you made *every* Java backend framework legible. Pair Jakarta EE with some Spring fluency and you can work almost anywhere in Java backend development. That's a strong, durable place to stand.

## What to build, and a last word

Reading got you here. Building is what makes it yours, and you already have the perfect starting point: the small `Product` service you grew across this guide. Pick one of these and take it further:

- **Make it cloud-ready.** Add MicroProfile **Health**, **Config**, and **OpenAPI**. Suddenly your service has probes Kubernetes understands, configuration that comes from the environment, and documentation that writes itself.
- **Feel the fast-startup model.** Rebuild the same service on **Quarkus**. Watching it boot in a fraction of a second is the moment the "modern Jakarta EE" idea stops being abstract.
- **Add token security.** Wire in MicroProfile **JWT** so callers authenticate with signed tokens - the way real microservices talk to each other.

When you want to go deeper, the official **Jakarta EE** and **MicroProfile** sites publish the specs and tutorials, and they're maintained by the people who build them - thorough and trustworthy.

Here's the thing to carry with you. Enterprise Java used to look like magic from the outside: annotations that somehow inject dependencies, manage transactions, and secure endpoints, with no visible wiring. You know now that it isn't magic. It's a careful set of open specifications, and you understand how each one works and where it fits. That understanding is the real skill - far more lasting than any single framework. Go build the small thing, ship it, and trust that you've earned the foundation. You're ready.

## Recap

1. **You can build a complete standard service** - CDI, JAX-RS, JPA, transactions, validation, and security - and you understand the spec-and-server model underneath it.
2. **MicroProfile adds the cloud-native pieces** classic Jakarta EE lacked: Config, Health, Metrics, OpenAPI, Fault Tolerance, and JWT - Jakarta EE plus MicroProfile is a full modern microservices stack on open standards.
3. **Modern runtimes** - Quarkus, Helidon, Open Liberty, Payara Micro, WildFly - implement these specs as fast, small, self-contained apps; they're the standards-based answer to Spring Boot, with Quarkus a strong cloud-native pick.
4. **The skills travel and transfer** - they work across every server, they're heavily used in enterprise, and because Spring shares the same DNA, knowing the standard makes every Java framework easier to read.
5. **Build next:** add MicroProfile Health/Config/OpenAPI to your `Product` service, rebuild it on Quarkus, or add JWT auth - then ship it.

## Quick check

One last check on the big picture you just built:

```quiz
[
  {
    "q": "What does MicroProfile add on top of Jakarta EE?",
    "choices": [
      "Cloud-native pieces like Config, Health, Metrics, OpenAPI, Fault Tolerance, and JWT",
      "A replacement for CDI and JAX-RS that you use instead of Jakarta EE",
      "A single proprietary application server you're required to buy",
      "A different programming language for writing enterprise services"
    ],
    "answer": 0,
    "explain": "MicroProfile is a companion set of open specs that fills the cloud-native gaps classic Jakarta EE lacked - externalized config, health probes, metrics, API docs, fault tolerance, and token security - sitting alongside the specs you already know."
  },
  {
    "q": "What do runtimes like Quarkus, Helidon, and Open Liberty have in common?",
    "choices": [
      "They implement Jakarta EE and MicroProfile as fast, small, self-contained apps",
      "They each invent their own non-standard annotations you must relearn",
      "They only run on a single cloud provider's hardware",
      "They abandon Jakarta EE entirely in favor of Spring"
    ],
    "answer": 0,
    "explain": "These runtimes implement the same Jakarta EE and MicroProfile standards but run as lightweight, quick-starting, container-friendly applications - the standards-based answer to Spring Boot."
  },
  {
    "q": "Why are your Jakarta EE skills valuable across the broader Java world?",
    "choices": [
      "You learned the standard, so it transfers across every server and makes Spring legible too",
      "Because Jakarta EE code only ever runs on one specific application server",
      "Because no other Java framework reuses any of its ideas",
      "Because it replaces the need to ever understand Java itself"
    ],
    "answer": 0,
    "explain": "Learning the standard means your CDI and JAX-RS knowledge works across WildFly, Payara, Quarkus, Helidon, and more - and since Spring borrowed many of the same ideas, the standard makes every Java backend framework easier to read."
  }
]
```
