Jakarta EE 11 Cheat Sheet
Jakarta EE 11 (Juni 2025) — CDI, REST, JPA, Data, Security, Concurrency & mehr.
Was ist neu in Jakarta EE 11?
Neu: Jakarta Data 1.0 (Repository Pattern)
Minimum: Java SE 17, empfohlen Java SE 21 (Virtual Threads, Records)
Entfernt: JAXB, JAX-WS, SOAP, Managed Beans, CORBA
Deprecated: @Context in REST (→ @Inject), java.util.Date in JPA (→ java.time)
16 aktualisierte Specs:
Persistence 3.2, REST 4.0, CDI 4.1, Concurrency 3.1,
Security 4.0, Servlet 6.1, Faces 4.1, Validation 3.1,
JSON-B 3.0, JSON-P 2.1, Expression Language 6.0
CDI (Contexts & Dependency Injection) 4.1
Scopes
@ApplicationScoped // Ein Exemplar pro Applikation (Singleton-artig)
@RequestScoped // Ein Exemplar pro HTTP-Request
@SessionScoped // Ein Exemplar pro HTTP-Session
@ConversationScoped // Über mehrere Requests hinweg
@Dependent // Default: neue Instanz pro Injection Point
Injection
// Field Injection
@Inject
private UserService userService;
// Constructor Injection (empfohlen)
@Inject
public OrderController(OrderService orderService, UserService userService) {
this.orderService = orderService;
this.userService = userService;
}
// Qualifier für Disambiguierung
@Inject @Premium
private PricingService pricing;
// Named Bean (z.B. für Faces/EL)
@Named("orderBean")
@RequestScoped
public class OrderBean { ... }
Producer
@Produces
@ApplicationScoped
public DataSource createDataSource() {
// Factory für Nicht-CDI-Objekte
return dataSourceFromConfig();
}
@Produces @RequestScoped @Named("currentUser")
public User getCurrentUser() {
return securityContext.getCallerPrincipal();
}
Interceptors
// 1. Binding Annotation
@InterceptorBinding
@Retention(RUNTIME) @Target({TYPE, METHOD})
public @interface Logged {}
// 2. Interceptor
@Logged @Interceptor @Priority(Interceptor.Priority.APPLICATION)
public class LoggingInterceptor {
@AroundInvoke
public Object log(InvocationContext ctx) throws Exception {
System.out.println("→ " + ctx.getMethod().getName());
return ctx.proceed();
}
}
// 3. Anwenden
@Logged
public class OrderService { ... }
Events
// Feuern
@Inject Event<Order> orderEvent;
orderEvent.fire(new Order(...)); // synchron
orderEvent.fireAsync(new Order(...)); // asynchron
// Beobachten
void onOrder(@Observes Order order) { ... }
void onOrderAsync(@ObservesAsync Order order) { ... }
void afterCommit(@Observes(during = AFTER_SUCCESS) Order o) { ... }
Jakarta REST 4.0
Resource-Klasse
@Path("/products")
@ApplicationScoped
public class ProductResource {
@Inject ProductService service; // NEU: @Inject statt @Context
@GET
@Produces(MediaType.APPLICATION_JSON)
public List<Product> getAll() {
return service.findAll();
}
@GET @Path("/{id}")
public Product getById(@PathParam("id") long id) {
return service.findById(id)
.orElseThrow(NotFoundException::new);
}
@POST
@Consumes(MediaType.APPLICATION_JSON)
public Response create(@Valid Product p) {
service.save(p);
return Response.created(URI.create("/products/" + p.getId()))
.entity(p).build();
}
@PUT @Path("/{id}")
@Consumes(MediaType.APPLICATION_JSON)
public Product update(@PathParam("id") long id, Product p) {
return service.update(id, p);
}
@DELETE @Path("/{id}")
public void delete(@PathParam("id") long id) {
service.delete(id);
}
}
Parameter-Annotationen
@PathParam("id") // /items/{id}
@QueryParam("page") // ?page=2
@HeaderParam("Authorization")
@CookieParam("session")
@FormParam("name") // Form POST
@BeanParam // Parameter-Objekt
@DefaultValue("1") // Default-Wert
Filter & Exception Mapper
// Request Filter
@Provider
public class AuthFilter implements ContainerRequestFilter {
@Override
public void filter(ContainerRequestContext ctx) {
if (!isAuthorized(ctx)) {
ctx.abortWith(Response.status(401).build());
}
}
}
// Exception Mapper
@Provider
public class NotFoundMapper implements ExceptionMapper<NotFoundException> {
@Override
public Response toResponse(NotFoundException e) {
return Response.status(404)
.entity(Map.of("error", e.getMessage())).build();
}
}
SSE (Server-Sent Events)
@GET @Path("/events")
@Produces(MediaType.SERVER_SENT_EVENTS)
public void stream(@Inject SseEventSink sink, @Inject Sse sse) {
executor.submit(() -> {
sink.send(sse.newEvent("update", "data"));
sink.close();
});
}
Client API
Client client = ClientBuilder.newClient();
List<Product> products = client
.target("https://api.example.com/products")
.queryParam("category", "books")
.request(MediaType.APPLICATION_JSON)
.get(new GenericType<List<Product>>() {});
client.close();
Jakarta Persistence 3.2
EntityManager
@PersistenceContext
EntityManager em;
em.persist(entity); // INSERT
em.find(Product.class, id); // SELECT by PK
em.merge(entity); // UPDATE (detached → managed)
em.remove(entity); // DELETE
em.flush(); // SQL sofort ausführen
em.detach(entity); // aus Persistence Context lösen
em.refresh(entity); // aus DB neu laden
JPQL
// Basis
em.createQuery("SELECT p FROM Product p WHERE p.price > :price", Product.class)
.setParameter("price", 100.0)
.getResultList();
// Joins
"SELECT o FROM Order o JOIN o.items i WHERE i.product.name = :name"
// Aggregation
"SELECT p.category, COUNT(p), AVG(p.price) FROM Product p GROUP BY p.category"
// NEU in 3.2: UNION, INTERSECT, EXCEPT
"SELECT p FROM Product p WHERE p.active = true
UNION
SELECT p FROM Product p WHERE p.featured = true"
// NEU in 3.2: id() und version() Funktionen
"SELECT id(p), version(p) FROM Product p"
// NEU in 3.2: left(), right(), replace()
"SELECT left(p.name, 3) FROM Product p"
"SELECT replace(p.description, 'old', 'new') FROM Product p"
// NEU in 3.2: || String-Konkatenation
"SELECT p.firstName || ' ' || p.lastName FROM Person p"
Relationships
// One-to-Many / Many-to-One
@OneToMany(mappedBy = "order", cascade = CascadeType.ALL, fetch = FetchType.LAZY)
List<OrderItem> items;
@ManyToOne @JoinColumn(name = "order_id")
Order order;
// Many-to-Many
@ManyToMany
@JoinTable(name = "student_course",
joinColumns = @JoinColumn(name = "student_id"),
inverseJoinColumns = @JoinColumn(name = "course_id"))
Set<Course> courses;
// One-to-One
@OneToOne(mappedBy = "employee")
Address address;
Lifecycle Callbacks
@PrePersist void beforeInsert() { createdAt = Instant.now(); }
@PostPersist void afterInsert() { ... }
@PreUpdate void beforeUpdate() { updatedAt = Instant.now(); }
@PostUpdate void afterUpdate() { ... }
@PreRemove void beforeDelete() { ... }
@PostLoad void afterLoad() { ... }
Neu in Persistence 3.2
// Programmatische Konfiguration (ohne persistence.xml)
EntityManagerFactory emf = new PersistenceConfiguration("myPU")
.jtaDataSource("java:global/jdbc/MyDS")
.managedClass(Customer.class)
.property(PersistenceConfiguration.LOCK_TIMEOUT, 5000)
.createEntityManagerFactory();
// Transaction Helpers
emf.runInTransaction(em -> {
em.persist(new Customer("Alice"));
});
String name = emf.callInTransaction(em -> {
return em.find(Customer.class, 1L).getName();
});
// Java Records als Embeddable
@Embeddable
public record Address(String street, String city, String zip) {}
// java.time Support
@Entity
public class Event {
private Instant createdAt; // Instant direkt unterstützt
private Year releaseYear; // Year direkt unterstützt
}
Jakarta Data 1.0 (NEU)
Repository Pattern — Provider-agnostisch (JPA, NoSQL).
Repository definieren
@Repository
public interface ProductRepository extends CrudRepository<Product, Long> {
// Derived Query (aus Methodenname)
List<Product> findByCategory(String category);
List<Product> findByPriceLessThan(double maxPrice);
Optional<Product> findByName(String name);
long countByCategory(String category);
boolean existsByName(String name);
// Mit Sortierung
List<Product> findByCategory(String category, Sort<?>... sorts);
// Mit Paginierung
@Find @OrderBy("name")
Page<Product> byCategory(String category, PageRequest pageRequest);
// JDQL Query
@Query("WHERE name LIKE :pattern AND active = true")
List<Product> search(String pattern, Limit max);
// JPQL Query (bei JPA-Backend)
@Query("SELECT p FROM Product p WHERE p.price BETWEEN :min AND :max ORDER BY p.price")
List<Product> findInPriceRange(double min, double max);
// Update Query
@Query("UPDATE Product SET price = price * :factor WHERE category = :cat")
int updatePriceByCategory(String cat, double factor);
// Explizite Lifecycle-Annotationen
@Insert Product create(Product p);
@Insert List<Product> createAll(List<Product> products);
@Delete void remove(Product p);
}
Verwendung
@Inject ProductRepository repo;
// CRUD
Product p = new Product("Widget", 9.99, "tools");
repo.create(p);
Optional<Product> found = repo.findByName("Widget");
repo.remove(p);
// Paginierung
PageRequest page = PageRequest.ofPage(1).size(20);
Page<Product> results = repo.byCategory("tools", page);
long total = results.totalElements();
List<Product> content = results.content();
// Cursor-basierte Paginierung
CursoredPage<Product> cursored = repo.findAll(pageRequest, Order.by(Sort.asc("id")));
PageRequest next = cursored.nextPageRequest();
// Sortierung
repo.findByCategory("books", Sort.asc("price"), Sort.desc("name"));
Repository-Hierarchie
DataRepository<T, K> Marker-Interface
└─ BasicRepository<T, K> @Save, @Find, @Delete
└─ CrudRepository<T, K> + @Insert, @Update
Jakarta Security 4.0
Authentication
// Basic Auth
@BasicAuthenticationMechanismDefinition(realmName = "myRealm")
// Form Auth
@FormAuthenticationMechanismDefinition(
loginToContinue = @LoginToContinue(
loginPage = "/login.xhtml",
errorPage = "/error.xhtml"))
// OpenID Connect
@OpenIdAuthenticationMechanismDefinition(
providerURI = "https://provider.example.com",
clientId = "myClient",
clientSecret = "mySecret",
redirectURI = "${baseURL}/callback",
scope = {"openid", "email", "profile"})
SecurityContext
@Inject SecurityContext sec;
Principal caller = sec.getCallerPrincipal();
boolean isAdmin = sec.isCallerInRole("admin");
Set<String> roles = sec.getAllDeclaredCallerRoles(); // NEU in 4.0
Jakarta Validation 3.1
// Auf Feldern
@NotNull // nicht null
@NotEmpty // nicht null und nicht leer
@NotBlank // nicht null und nicht nur Whitespace
@Size(min = 2, max = 100) // Länge/Größe
@Min(0) @Max(999) // Zahlenbereiche
@Positive @PositiveOrZero
@Email // E-Mail-Format
@Pattern(regexp = "^[A-Z].*") // Regex
@Past @Future // Datum
@Digits(integer = 5, fraction = 2)
// Auf Methoden-Parametern
public @NotNull Order place(@NotNull @Valid OrderRequest req) { ... }
// Custom Validator
@Constraint(validatedBy = PhoneValidator.class)
@Target({FIELD, PARAMETER}) @Retention(RUNTIME)
public @interface ValidPhone {
String message() default "Invalid phone";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
}
public class PhoneValidator implements ConstraintValidator<ValidPhone, String> {
public boolean isValid(String val, ConstraintValidatorContext ctx) {
return val != null && val.matches("\\+?[0-9]{10,15}");
}
}
// NEU: Java Records werden unterstützt
Jakarta JSON-B 3.0
Jsonb jsonb = JsonbBuilder.create();
// Serialisieren / Deserialisieren
String json = jsonb.toJson(product);
Product p = jsonb.fromJson(json, Product.class);
// Konfiguration
JsonbConfig config = new JsonbConfig()
.withFormatting(true)
.withNullValues(false)
.withPropertyNamingStrategy(PropertyNamingStrategy.LOWER_CASE_WITH_UNDERSCORES);
Jsonb jsonb = JsonbBuilder.create(config);
Annotationen
@JsonbProperty("full_name") // JSON-Feldname
@JsonbTransient // ausschließen
@JsonbDateFormat("yyyy-MM-dd") // Datumsformat
@JsonbNumberFormat("#0.00") // Zahlenformat
@JsonbNillable // null-Werte einschließen
@JsonbPropertyOrder({"id", "name"}) // Reihenfolge
@JsonbCreator // Custom Deserialisierung
NEU: Polymorphie
@JsonbTypeInfo(key = "@type", value = {
@JsonbSubtype(alias = "dog", type = Dog.class),
@JsonbSubtype(alias = "cat", type = Cat.class)
})
public abstract class Animal { ... }
// {"@type":"dog","name":"Rex"} → Dog-Instanz
Jakarta Concurrency 3.1
Virtual Threads
// Executor mit Virtual Threads definieren
@ManagedExecutorDefinition(
name = "java:app/concurrent/virtualExecutor",
maxAsync = 10,
virtual = true) // Virtual Threads auf Java 21+
@ManagedScheduledExecutorDefinition(
name = "java:app/concurrent/scheduler",
virtual = true)
Async & Scheduling
// CDI-basiertes @Asynchronous (ersetzt EJB)
@Asynchronous
public CompletableFuture<Report> generateReport(Long id) {
Report r = buildReport(id);
return Asynchronous.Result.complete(r);
}
// Scheduling (ersetzt EJB @Schedule)
@Asynchronous(runAt = @Schedule(cron = "0 8 * * MON-FRI"))
public void morningJob() {
// Läuft Mo-Fr um 08:00
}
Quick Reference
Spec Version Highlights
───────────────────────── ──────── ─────────────────────────────
Jakarta Data 1.0 Repository Pattern (NEU)
Jakarta Persistence 3.2 UNION, Records, ohne persistence.xml
Jakarta REST 4.0 @Context → @Inject, JSON Merge Patch
Jakarta CDI 4.1 CDI Lite, Verbesserungen
Jakarta Concurrency 3.1 Virtual Threads, @Schedule
Jakarta Security 4.0 OIDC, Multiple Auth Mechanisms
Jakarta Servlet 6.1 ByteBuffer, Virtual Threads
Jakarta Validation 3.1 Records Support
Jakarta JSON-B 3.0 Polymorphie
Jakarta Faces 4.1 UUIDConverter, CDI Alignment