From 630136888493dd4e316f8c6335cc398575892197 Mon Sep 17 00:00:00 2001 From: emijiang Date: Wed, 6 May 2026 18:29:21 +0100 Subject: [PATCH 01/26] Add Jakarta EE 11 blog --- ...6-05-16-Jakarta EE 11 in Open Liberty.adoc | 2098 +++++++++++++++++ 1 file changed, 2098 insertions(+) create mode 100644 posts/2026-05-16-Jakarta EE 11 in Open Liberty.adoc diff --git a/posts/2026-05-16-Jakarta EE 11 in Open Liberty.adoc b/posts/2026-05-16-Jakarta EE 11 in Open Liberty.adoc new file mode 100644 index 000000000..50b6603dd --- /dev/null +++ b/posts/2026-05-16-Jakarta EE 11 in Open Liberty.adoc @@ -0,0 +1,2098 @@ +--- +layout: post +title: "Jakarta EE 11 from Newbie to Pro with Open Liberty" +# Do NOT change the categories section +categories: blog +author_picture: https://avatars3.githubusercontent.com/emily-jiang +author_github: https://github.com/Emily-Jiang +seo-title: Jakarta EE 11 from Newbie to Pro with Open Liberty +seo-description: Discover Jakarta EE 11's revolutionary features in Open Liberty 26.0.0.5 or later releases, including the game-changing Jakarta Data 1.0 specification that eliminates boilerplate code. Explore comprehensive code examples for updated specifications like CDI 4.1, Persistence 3.2, Security 4.0, and RESTful Web Services 4.0. Learn how Java 21 Records further simplify your applications. +blog_description: Discover Jakarta EE 11's revolutionary features in Open Liberty 26.0.0.5 or later releases, including the game-changing Jakarta Data 1.0 specification that eliminates boilerplate code. Explore comprehensive code examples for updated specifications like CDI 4.1, Persistence 3.2, Security 4.0, and RESTful Web Services 4.0. Learn how Java 21 Records further simplify your applications. +open-graph-image: https://openliberty.io/img/twitter_card.jpg +open-graph-image-alt: Open Liberty Logo +--- += TITLE +Emily Jiang +:imagesdir: / +:url-prefix: +:url-about: / +//Blank line here is necessary before starting the body of the post. + +// // // // // // // // +// In the preceding section: +// Do not insert any blank lines between any of the lines. +// +// "open-graph-image" is set to OL logo. Whenever possible update this to a more appropriate/specific image (For example if present a image that is being used in the post). However, it +// can be left empty which will set it to the default +// +// "open-graph-image-alt" is a description of what is in the image (not a caption). When changing "open-graph-image" to +// a custom picture, you must provide a custom string for "open-graph-image-alt". +// +// Replace TITLE with the blog post title. +// Replace AUTHOR_NAME with your name as first author. +// Replace GITHUB_USERNAME with your GitHub username eg: lauracowen +// Replace DESCRIPTION with a short summary (~60 words) of the release (a more succinct version of the first paragraph of the post). +// +// Replace AUTHOR_NAME with your name as you'd like it to be displayed, eg: Laura Cowen +// +// Example post: 2020-04-02-generate-microprofile-rest-client-code.adoc +// +// If adding image into the post add : +// ------------------------- +// [.img_border_light] +// image::img/blog/FILE_NAME[IMAGE CAPTION ,width=70%,align="center"] +// ------------------------- +// "[.img_border_light]" = This adds a faint grey border around the image to make its edges sharper. Use it around screenshots but not +// around diagrams. Then double check how it looks. +// There is also a "[.img_border_dark]" class which tends to work best with screenshots that are taken on dark backgrounds. +// Change "FILE_NAME" to the name of the image file. Also make sure to put the image into the right folder which is: img/blog +// change the "IMAGE CAPTION" to a couple words of what the image is +// // // // // // // // + +link:https://jakarta.ee/release/11/[Jakarta EE 11] marks a significant milestone in the evolution of enterprise Java development. This release delivers enhanced developer productivity, improved performance, and modernized APIs that align with the Java LTS releases: Java SE 17, Java SE 21. + +== What's New in Jakarta EE 11? + +Jakarta EE 11 represents a major step forward for cloud-native enterprise Java applications. This release includes modernizing and restructuring the Test Compatibility Kits (TCKs), the new Jakarta Data specification, major updates to existing specifications, and support for the latest Java LTS release, which enables developers to leverage enhancements in Java 21, including Virtual Threads, Records. Full lists are: +* <> +* <> +* <> +* <> +* <> +* <> +*<> +* <> +* <> +* <> +* <> +* <> +* <> + + +[#data-1.0] +== Jakarta Data 1.0: A Game Changer for Data Access + +One of the most exciting additions to Jakarta EE 11 is *Jakarta Data 1.0*, a new specification that revolutionizes how developers interact with databases in enterprise applications. + +=== What is Jakarta Data? + +Jakarta Data provides an API for easier data access. A Java developer can split the persistence from the model with several features, such as the ability to compose custom query methods or a Repository interface. Jakarta Data's goal is to provide a familiar and consistent, Jakarta-based programming model for data access while still retaining the particular traits of the underlying data store. + +=== Key Features of Jakarta Data 1.0 + +Jakarta Data 1.0 includes: + +. *Repository Pattern*: Define data access through simple interfaces +. *Query by Method Name*: Automatic query generation from method names (e.g., `findByType`, `findByName`) +. *Type Safety with StaticMetamodel*: Enhanced type safety for queries +. *Pagination Support*: Built-in pagination on Repository +. *Platform Integrations*: Works with CDI, Persistence, NoSQL, Transactions, and Validation +. *Entity Support*: Works with persistence and nosql entities + +=== Jakarta Data Code Examples + +==== Defining an Entity + +[source,java] +---- +import jakarta.data.Entity; +import jakarta.data.Id; + +@Entity +public class Product { + @Id + private Long id; + private String name; + private String description; + private double price; + private int stockQuantity; + + // Constructors, getters, and setters + public Product() {} + + public Product(String name, double price) { + this.name = name; + this.price = price; + } + + // Getters and setters omitted for brevity +} +---- + +==== Creating a Repository Interface + +[source,java] +---- +import jakarta.data.repository.Repository; +import jakarta.data.repository.Find; +import jakarta.data.repository.Query; +import jakarta.data.repository.Save; +import jakarta.data.repository.Delete; +import jakarta.data.repository.Page; +import jakarta.data.repository.Pageable; +import java.util.List; +import java.util.Optional; + +@Repository +public interface ProductRepository { + + // Basic CRUD operations + @Save + Product save(Product product); + + @Find + Optional findById(Long id); + + @Delete + void delete(Product product); + + // Query by method name - automatically generates query + List findByName(String name); + + List findByPriceLessThan(double price); + + List findByPriceBetween(double minPrice, double maxPrice); + + List findByNameContaining(String keyword); + + // Sorting and pagination + Page findByPriceGreaterThan(double price, Pageable pageable); + + // Custom queries using @Query annotation + @Query("SELECT p FROM Product p WHERE p.stockQuantity < 10") + List findLowStockProducts(); + + @Query("SELECT p FROM Product p WHERE p.price > ?1 ORDER BY p.price DESC") + List findExpensiveProducts(double minPrice); +} +---- + +==== Using the Repository in a Service + +[source,java] +---- +import jakarta.enterprise.context.ApplicationScoped; +import jakarta.inject.Inject; +import jakarta.data.repository.Pageable; +import jakarta.data.repository.Page; +import java.util.List; + +@ApplicationScoped +public class ProductService { + + @Inject + private ProductRepository productRepository; + + public Product createProduct(String name, double price) { + Product product = new Product(name, price); + return productRepository.save(product); + } + + public List searchProducts(String keyword) { + return productRepository.findByNameContaining(keyword); + } + + public Page getExpensiveProducts(double minPrice, int pageNumber, int pageSize) { + Pageable pageable = Pageable.ofSize(pageSize).page(pageNumber); + return productRepository.findByPriceGreaterThan(minPrice, pageable); + } + + public List getLowStockProducts() { + return productRepository.findLowStockProducts(); + } +} +---- + +==== REST Endpoint Using Jakarta Data + +[source,java] +---- +import jakarta.ws.rs.*; +import jakarta.ws.rs.core.MediaType; +import jakarta.ws.rs.core.Response; +import jakarta.inject.Inject; +import jakarta.data.repository.Page; +import java.util.List; + +@Path("/products") +@Produces(MediaType.APPLICATION_JSON) +@Consumes(MediaType.APPLICATION_JSON) +public class ProductResource { + + @Inject + private ProductService productService; + + @POST + public Response createProduct(Product product) { + Product created = productService.createProduct( + product.getName(), + product.getPrice() + ); + return Response.status(Response.Status.CREATED).entity(created).build(); + } + + @GET + @Path("/search") + public List searchProducts(@QueryParam("keyword") String keyword) { + return productService.searchProducts(keyword); + } + + @GET + @Path("/expensive") + public Page getExpensiveProducts( + @QueryParam("minPrice") @DefaultValue("100.0") double minPrice, + @QueryParam("page") @DefaultValue("0") int page, + @QueryParam("size") @DefaultValue("20") int size) { + return productService.getExpensiveProducts(minPrice, page, size); + } + + @GET + @Path("/low-stock") + public List getLowStockProducts() { + return productService.getLowStockProducts(); + } +} +---- + +=== Benefits of Jakarta Data + +- *Reduced Boilerplate*: No need to write repetitive DAO/repository implementations +- *Type Safety*: Compile-time checking of queries and method signatures +- *Database Flexibility*: Switch between databases without changing application code +- *Improved Productivity*: Focus on business logic instead of data access code +- *Standardization*: Vendor-neutral API backed by the Jakarta EE community + +== Updated Specifications with Code Examples + +[#authentication-3.1] +=== Authentication 3.1 + +Jakarta Authentication 3.1 defines a general low-level SPI for authentication mechanisms, which are controllers that interact with a caller and a container's environment to obtain the caller's credentials, validate these, and pass an authenticated identity (such as name and groups) to the container. + +*Key changes in Authentication 3.1:* +- Removes references to the SecurityManager (aligned with Java's deprecation and removal of SecurityManager) +- Evolves the API in a smaller way to support the overall goals of Jakarta Security +- Consists of several profiles, with each profile telling how a specific container (such as Jakarta Servlet) can integrate with and adapt to this SPI + +The 3.1 version removes the deprecated Permission-related fields in the `jakarta.security.auth.message.config.AuthConfigFactory` class. The methods in the class no longer do permission checking. These changes are part of Jakarta EE 11's removal of SecurityManager support. + + +==== Authentication 3.1 Code Example + +[source,java] +---- +import jakarta.security.auth.message.AuthException; +import jakarta.security.auth.message.AuthStatus; +import jakarta.security.auth.message.MessageInfo; +import jakarta.security.auth.message.MessagePolicy; +import jakarta.security.auth.message.module.ServerAuthModule; +import jakarta.security.auth.message.callback.CallerPrincipalCallback; +import jakarta.security.auth.message.callback.GroupPrincipalCallback; +import javax.security.auth.Subject; +import javax.security.auth.callback.CallbackHandler; +import javax.security.auth.callback.Callback; +import java.util.Map; + +public class CustomServerAuthModule implements ServerAuthModule { + + private CallbackHandler handler; + + @Override + public void initialize(MessagePolicy requestPolicy, MessagePolicy responsePolicy, + CallbackHandler handler, Map options) throws AuthException { + this.handler = handler; + } + + @Override + public AuthStatus validateRequest(MessageInfo messageInfo, Subject clientSubject, + Subject serviceSubject) throws AuthException { + // Extract credentials from request + String username = extractUsername(messageInfo); + String password = extractPassword(messageInfo); + + if (validateCredentials(username, password)) { + // Set the caller principal + CallerPrincipalCallback principalCallback = + new CallerPrincipalCallback(clientSubject, username); + + // Set the groups + GroupPrincipalCallback groupCallback = + new GroupPrincipalCallback(clientSubject, new String[]{"users"}); + + try { + handler.handle(new Callback[]{principalCallback, groupCallback}); + } catch (Exception e) { + throw new AuthException(e.getMessage()); + } + + return AuthStatus.SUCCESS; + } + + return AuthStatus.FAILURE; + } + + @Override + public AuthStatus secureResponse(MessageInfo messageInfo, Subject serviceSubject) + throws AuthException { + return AuthStatus.SEND_SUCCESS; + } + + @Override + public void cleanSubject(MessageInfo messageInfo, Subject subject) throws AuthException { + if (subject != null) { + subject.getPrincipals().clear(); + } + } + + @Override + public Class[] getSupportedMessageTypes() { + return new Class[]{jakarta.servlet.http.HttpServletRequest.class, + jakarta.servlet.http.HttpServletResponse.class}; + } + + private String extractUsername(MessageInfo messageInfo) { + // Extract username from request + return "user"; + } + + private String extractPassword(MessageInfo messageInfo) { + // Extract password from request + return "password"; + } + + private boolean validateCredentials(String username, String password) { + // Validate credentials + return username != null && password != null; + } +} +---- +[#authorization-3.0] +=== Authorization 3.0 + +Jakarta Authorization 3.0 defines an SPI for authorization modules, which are repositories of permissions that facilitate subject-based security by determining whether a subject has a specific permission. + +The 3.0 API introduces the new `jakarta.security.jacc.PolicyFactory` and `jakarta.security.jacc.Policy` classes for doing authorization decisions. These classes are added to remove the dependency on the `java.security.Policy` class, which is deprecated in newer versions of Java. With the new `PolicyFactory` API, you can now have a `Policy` per policy context instead of having a global policy. This design allows separate policies to be maintained for each application. + +Additionally, the 3.0 specification defines a mechanism to define `PolicyConfigurationFactory` and `PolicyFactory` classes in your application by using a `web.xml` file. This design allows for an application to have a different configuration than the server-scoped one, but still allow for it to delegate to a server scoped factory for any other applications. Authorization modules can do this delegation by using decorator constructors for both `PolicyConfigurationFactory` and `PolicyFactory` classes. + +To configure your authorization modules in your application's `web.xml` file, add specification defined context parameters: + +[source,xml] +---- + + + + + jakarta.security.jacc.PolicyConfigurationFactory.provider + com.example.MyPolicyConfigurationFactory + + + + jakarta.security.jacc.PolicyFactory.provider + com.example.MyPolicyFactory + + + +---- + +Due to Jakarta Authorization 3.0 no longer using the `java.security.Policy` class and introducing a new configuration mechanism for authorization modules, the `com.ibm.wsspi.security.authorization.jacc.ProviderService` Liberty API is no longer available with the appAuthorization-3.0 feature. If a Liberty user feature configures authorization modules, the OSGi service that provided a `ProviderService` implementation must be updated to use the `PolicyConfigurationFactory` and `PolicyFactory` set methods. These methods configure the modules in the OSGi service. Alternatively, you can use a Web Application Bundle (WAB) in your user feature to specify your security modules in a `web.xml` file. + +Finally, the 3.0 API adds a new `jakarta.security.jacc.PrincipalMapper` class that you can obtain from the `PolicyContext` class when authorization processing is done in your `Policy` implementation. From this class, you can obtain the roles that are associated with a specific Subject to be able to determine whether the Subject is in the required role. + +You can use the `PrincipalMapper` class in your `Policy` implementation's `impliesByRole` (or `implies`) method, as shown in the following example: + +[source,java] +---- +public boolean impliesByRole(Permission p, Subject subject) { + Map perRolePermissions = + PolicyConfigurationFactory.get().getPolicyConfiguration(contextID).getPerRolePermissions(); + PrincipalMapper principalMapper = PolicyContext.get(PolicyContext.PRINCIPAL_MAPPER); + + // Check to see if the Permission is in the all authenticated users role + if (!principalMapper.isAnyAuthenticatedUserRoleMapped() && !subject.getPrincipals().isEmpty()) { + PermissionCollection rolePermissions = perRolePermissions.get("**"); + if (rolePermissions != null && rolePermissions.implies(p)) { + return true; + } + } + + // Check to see if the roles for the Subject provided imply the permission + Set mappedRoles = principalMapper.getMappedRoles(subject); + for (String mappedRole : mappedRoles) { + PermissionCollection rolePermissions = perRolePermissions.get(mappedRole); + if (rolePermissions != null && rolePermissions.implies(p)) { + return true; + } + } + + return false; +} +---- + +[#concurrency-3.1] +=== Concurrency 3.1 + +Enhanced concurrency utilities with better Virtual Thread support for Java 21+. + +[source,java] +---- +import jakarta.enterprise.concurrent.ManagedExecutorService; +import jakarta.enterprise.concurrent.ManagedScheduledExecutorService; +import jakarta.annotation.Resource; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.TimeUnit; + +public class ConcurrencyExample { + + @Resource + private ManagedExecutorService executorService; + + @Resource + private ManagedScheduledExecutorService scheduledExecutor; + + public CompletableFuture asyncOperation() { + // Leverages Virtual Threads on Java 21+ + return CompletableFuture.supplyAsync(() -> { + // Long-running operation + return processData(); + }, executorService); + } + + public void scheduleTask() { + // Schedule a task to run periodically + scheduledExecutor.scheduleAtFixedRate( + () -> performMaintenanceTask(), + 0, 1, TimeUnit.HOURS + ); + } + + private String processData() { + return "Processed data"; + } + + private void performMaintenanceTask() { + // Maintenance logic + } +} +---- + +[#cdi-4.1] +=== CDI 4.1 + +Jakarta Contexts and Dependency Injection (CDI) 4.1 specifies a means for obtaining objects in such a way as to maximize reusability, testability and maintainability compared to traditional approaches such as constructors, factories, and service locators (e.g., JNDI). + +*New features in CDI 4.1:* + +- *Method invokers* - New API for programmatic method invocation with interceptor support +- *Executable methods* - Enhanced method execution capabilities +- *@Priority on producers* - Ability to specify priority on producer methods and fields +- *Getting interceptor bindings in standard way* - Standardized access to interceptor binding information +- *Breaking up spec/TCK* - Removed circular dependencies between specification and TCK +- *Delegate integration requirements* - Delegated integration requirements to Jakarta Platform specifications +- *Improved managed bean requirements* - Clearer wording and requirements for managed beans + +==== Method Invokers Example + +Method invokers provide a way to invoke methods programmatically while preserving CDI interceptor behavior: + +[source,java] +---- +import jakarta.enterprise.invoke.Invoker; +import jakarta.enterprise.invoke.InvokerBuilder; +import jakarta.inject.Inject; +import jakarta.enterprise.context.ApplicationScoped; + +@ApplicationScoped +public class InvokerExample { + + @Inject + InvokerBuilder invokerBuilder; + + public void demonstrateInvoker() throws Exception { + // Build an invoker for a specific method + Invoker invoker = invokerBuilder + .setMethod(MyService.class.getMethod("processData", String.class)) + .build(); + + MyService service = new MyService(); + + // Invoke the method - interceptors will be applied + String result = invoker.invoke(service, new Object[]{"input data"}); + System.out.println("Result: " + result); + } +} + +@ApplicationScoped +class MyService { + + @Logged // Custom interceptor binding + public String processData(String input) { + return "Processed: " + input; + } +} +---- + +==== @Priority on Producers Example + +CDI 4.1 allows you to specify priority on producer methods and fields to control which producer is selected when multiple producers exist: + +[source,java] +---- +import jakarta.enterprise.inject.Produces; +import jakarta.enterprise.context.ApplicationScoped; +import jakarta.annotation.Priority; + +@ApplicationScoped +public class ConfigurationProducers { + + // Default configuration with lower priority + @Produces + @Priority(100) + public DatabaseConfig defaultConfig() { + DatabaseConfig config = new DatabaseConfig(); + config.setUrl("jdbc:h2:mem:testdb"); + return config; + } + + // Production configuration with higher priority (will be selected) + @Produces + @Priority(200) + public DatabaseConfig productionConfig() { + DatabaseConfig config = new DatabaseConfig(); + config.setUrl("jdbc:postgresql://prod-server:5432/proddb"); + return config; + } +} + +class DatabaseConfig { + private String url; + public void setUrl(String url) { this.url = url; } + public String getUrl() { return url; } +} +---- + +==== Getting Interceptor Bindings Example + +CDI 4.1 provides a standardized way to access interceptor binding information: + +[source,java] +---- +import jakarta.enterprise.inject.spi.BeanManager; +import jakarta.enterprise.inject.spi.InterceptorBinding; +import jakarta.inject.Inject; +import jakarta.interceptor.InterceptorBinding; +import java.lang.annotation.Retention; +import java.lang.annotation.Target; +import static java.lang.annotation.ElementType.*; +import static java.lang.annotation.RetentionPolicy.RUNTIME; + +@InterceptorBinding +@Retention(RUNTIME) +@Target({TYPE, METHOD}) +public @interface Logged { +} + +@ApplicationScoped +public class InterceptorBindingExample { + + @Inject + BeanManager beanManager; + + public void checkInterceptorBindings() { + // Get interceptor bindings for a specific annotation + Set bindings = beanManager + .resolveInterceptorBindings(Logged.class); + + bindings.forEach(binding -> + System.out.println("Found binding: " + binding)); + } +} +---- + +[#expression-language-6.0] +=== Expression Language 6.0 + +Jakarta Expression Language 6.0 defines an expression language for Java applications. This release makes the dependency on the java.desktop module optional, removes references to the SecurityManager, and provides a small number of usability improvements. + +*New features in Expression Language 6.0:* + +- *java.desktop module no longer required*: The java.desktop module is no longer required at runtime, improving modularity +- *New `length` property for arrays*: A new property, `length`, is now supported for arrays, making it easier to get array sizes in EL expressions +- *Java Records support*: Added support, enabled by default, for `java.lang.Record` instances via the new `RecordELResolver` +- *Java Optional support*: Added support, disabled by default, for `java.lang.Optional` instances via the new `OptionalELResolver` + +*Removals:* +- All code deprecated as of Expression Language 5.0 has been removed, specifically the `getFeatureDescriptors()` method from the `ELResolver` interface +- All references to the Java SecurityManager and associated APIs have been removed + +==== Using the new `length` property for arrays + +[source,java] +---- +import jakarta.el.*; +import java.util.Arrays; + +public class ArrayLengthExample { + + public static void main(String[] args) { + // Create EL context + ExpressionFactory factory = ExpressionFactory.newInstance(); + StandardELContext context = new StandardELContext(factory); + + // Create an array and add it to the context + String[] fruits = {"Apple", "Banana", "Cherry", "Date", "Elderberry"}; + context.getELResolver().setValue(context, null, "fruits", fruits); + + // NEW in EL 6.0: Use the 'length' property to get array size + ValueExpression lengthExpr = factory.createValueExpression( + context, "${fruits.length}", Integer.class); + Integer length = (Integer) lengthExpr.getValue(context); + + System.out.println("Array length: " + length); // Output: 5 + + // You can also use it in conditional expressions + ValueExpression hasItemsExpr = factory.createValueExpression( + context, "${fruits.length > 0}", Boolean.class); + Boolean hasItems = (Boolean) hasItemsExpr.getValue(context); + + System.out.println("Has items: " + hasItems); // Output: true + } +} +---- + +==== Using RecordELResolver for Java Records + +Java Records are now supported by default in EL 6.0: + +[source,java] +---- +import jakarta.el.*; + +// Define a Java Record +public record Product(String name, double price, int quantity) { + public double totalValue() { + return price * quantity; + } +} + +public class RecordELResolverExample { + + public static void main(String[] args) { + // Create EL context + ExpressionFactory factory = ExpressionFactory.newInstance(); + StandardELContext context = new StandardELContext(factory); + + // Create a Record instance + Product product = new Product("Laptop", 999.99, 5); + context.getELResolver().setValue(context, null, "product", product); + + // NEW in EL 6.0: Access Record components directly + ValueExpression nameExpr = factory.createValueExpression( + context, "${product.name}", String.class); + String name = (String) nameExpr.getValue(context); + System.out.println("Product name: " + name); // Output: Laptop + + ValueExpression priceExpr = factory.createValueExpression( + context, "${product.price}", Double.class); + Double price = (Double) priceExpr.getValue(context); + System.out.println("Product price: " + price); // Output: 999.99 + + // Access Record methods + ValueExpression totalExpr = factory.createValueExpression( + context, "${product.totalValue()}", Double.class); + Double total = (Double) totalExpr.getValue(context); + System.out.println("Total value: " + total); // Output: 4999.95 + } +} +---- + +==== Using OptionalELResolver for Java Optional + +Support for `java.lang.Optional` is available but disabled by default. To enable it, you need to add the `OptionalELResolver` to your EL context: + +[source,java] +---- +import jakarta.el.*; +import java.util.Optional; + +public class OptionalELResolverExample { + + public static void main(String[] args) { + // Create EL context + ExpressionFactory factory = ExpressionFactory.newInstance(); + StandardELContext context = new StandardELContext(factory); + + // NEW in EL 6.0: Add OptionalELResolver to support Optional + // Note: This is disabled by default and must be explicitly added + CompositeELResolver resolver = new CompositeELResolver(); + resolver.add(new OptionalELResolver()); + resolver.add(new BeanELResolver()); + resolver.add(new ArrayELResolver()); + resolver.add(new ListELResolver()); + resolver.add(new MapELResolver()); + context.addELResolver(resolver); + + // Create Optional values + Optional presentValue = Optional.of("Hello, EL 6.0!"); + Optional emptyValue = Optional.empty(); + + context.getELResolver().setValue(context, null, "message", presentValue); + context.getELResolver().setValue(context, null, "empty", emptyValue); + + // Access Optional values - automatically unwrapped if present + ValueExpression messageExpr = factory.createValueExpression( + context, "${message}", String.class); + String message = (String) messageExpr.getValue(context); + System.out.println("Message: " + message); // Output: Hello, EL 6.0! + + // Empty Optional returns null + ValueExpression emptyExpr = factory.createValueExpression( + context, "${empty}", String.class); + String emptyResult = (String) emptyExpr.getValue(context); + System.out.println("Empty: " + emptyResult); // Output: null + + // Use with conditional expressions + ValueExpression hasMsgExpr = factory.createValueExpression( + context, "${message != null}", Boolean.class); + Boolean hasMessage = (Boolean) hasMsgExpr.getValue(context); + System.out.println("Has message: " + hasMessage); // Output: true + } +} +---- + +==== Using EL 6.0 in JSF/Facelets + +[source,xhtml] +---- + + + + + + + + + + + + + + + + +---- + +[#faces-4.1] +=== Faces 4.1 + +Jakarta Server Faces 4.1 defines an MVC framework for building user interfaces for web applications, including UI components, state management, event handling, input validation, page navigation, and support for internationalization and accessibility. This release removes references to the SecurityManager, further aligns with CDI where possible, and provides various small enhancements and clarifications. + +*New features in Faces 4.1:* + +- *Generic FacesMessage*: Make `FacesMessage#VALUES` / `VALUES_MAP` generic for better type safety +- *CDI event firing*: Require firing events for `@Initialized`, `@BeforeDestroyed`, `@Destroyed` for build-in scopes +- *Missing generics*: Add missing generics to API that were missed in Faces 4.0 +- *Flow injection*: Support `@Inject` of current flow like `@Inject Flow currentFlow` +- *UUIDConverter*: Add new converter for UUID types +- *ExternalContext enhancement*: Add `setResponseContentLengthLong` method for large content +- *UIRepeat enhancement*: Add `rowStatePreserved` property to UIRepeat, exactly the same as UIData +- *Development mode default*: `jakarta.faces.FACELETS_REFRESH_PERIOD` default when ProjectStage is Development +- *FacesMessage improvements*: Implement `equals()`, `hashcode()`, `toString()` methods + +*Removals:* +- Deprecate unused `composite.extension` +- Remove references to the SecurityManager + +==== Using the new UUIDConverter + +[source,java] +---- +import jakarta.faces.view.ViewScoped; +import jakarta.inject.Named; +import jakarta.faces.convert.UUIDConverter; +import java.io.Serializable; +import java.util.UUID; + +@Named +@ViewScoped +public class EntityBean implements Serializable { + + private UUID entityId; + private String entityName; + + public void init() { + // NEW in Faces 4.1: UUIDConverter automatically handles UUID conversion + // Generate a new UUID for the entity + entityId = UUID.randomUUID(); + } + + public void saveEntity() { + System.out.println("Saving entity with ID: " + entityId); + // The UUID is automatically converted to/from String in the view + } + + // Getters and setters + public UUID getEntityId() { return entityId; } + public void setEntityId(UUID entityId) { this.entityId = entityId; } + public String getEntityName() { return entityName; } + public void setEntityName(String entityName) { this.entityName = entityName; } +} +---- + +[source,xhtml] +---- + + + + + + + + + + + + + +---- + +==== Injecting the current Flow + +[source,java] +---- +import jakarta.faces.flow.Flow; +import jakarta.faces.view.ViewScoped; +import jakarta.inject.Inject; +import jakarta.inject.Named; +import java.io.Serializable; + +@Named +@ViewScoped +public class FlowAwareBean implements Serializable { + + // NEW in Faces 4.1: Direct injection of current Flow + @Inject + private Flow currentFlow; + + public String getFlowInfo() { + if (currentFlow != null) { + return "Current flow: " + currentFlow.getId(); + } + return "Not in a flow"; + } + + public boolean isInFlow() { + return currentFlow != null; + } + + public String getFlowId() { + return currentFlow != null ? currentFlow.getId() : null; + } +} +---- + +==== Using rowStatePreserved in UIRepeat + +[source,xhtml] +---- + + + + + + + + + +---- + +==== Using setResponseContentLengthLong for large files + +[source,java] +---- +import jakarta.faces.context.ExternalContext; +import jakarta.faces.context.FacesContext; +import jakarta.inject.Named; +import jakarta.enterprise.context.RequestScoped; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; + +@Named +@RequestScoped +public class FileDownloadBean { + + public void downloadLargeFile() throws IOException { + FacesContext facesContext = FacesContext.getCurrentInstance(); + ExternalContext externalContext = facesContext.getExternalContext(); + + // Simulate a large file (> 2GB) + long fileSize = 3_000_000_000L; // 3GB + + externalContext.responseReset(); + externalContext.setResponseContentType("application/octet-stream"); + externalContext.setResponseHeader("Content-Disposition", + "attachment; filename=\"largefile.bin\""); + + // NEW in Faces 4.1: setResponseContentLengthLong for files > 2GB + externalContext.setResponseContentLengthLong(fileSize); + + try (OutputStream output = externalContext.getResponseOutputStream()) { + // Write file content + // ... (implementation details) + } + + facesContext.responseComplete(); + } +} +---- + +==== Generic FacesMessage with improved type safety + +[source,java] +---- +import jakarta.faces.application.FacesMessage; +import jakarta.faces.context.FacesContext; +import jakarta.inject.Named; +import jakarta.enterprise.context.RequestScoped; + +@Named +@RequestScoped +public class MessageBean { + + public void demonstrateGenericMessages() { + FacesContext context = FacesContext.getCurrentInstance(); + + // NEW in Faces 4.1: FacesMessage.VALUES and VALUES_MAP are now generic + // This provides better type safety when working with message severities + + FacesMessage infoMsg = new FacesMessage( + FacesMessage.SEVERITY_INFO, + "Information", + "This is an info message" + ); + + FacesMessage warnMsg = new FacesMessage( + FacesMessage.SEVERITY_WARN, + "Warning", + "This is a warning message" + ); + + FacesMessage errorMsg = new FacesMessage( + FacesMessage.SEVERITY_ERROR, + "Error", + "This is an error message" + ); + + // NEW in Faces 4.1: FacesMessage now implements equals(), hashCode(), toString() + System.out.println(infoMsg.toString()); + + // Compare messages + FacesMessage anotherInfoMsg = new FacesMessage( + FacesMessage.SEVERITY_INFO, + "Information", + "This is an info message" + ); + + if (infoMsg.equals(anotherInfoMsg)) { + System.out.println("Messages are equal"); + } + + context.addMessage(null, infoMsg); + context.addMessage(null, warnMsg); + context.addMessage(null, errorMsg); + } +} +---- + +[#security-4.0] +=== Security 4.0 + +Jakarta Security 4.0 provides an In-memory Identity Store, which is a developer-defined store of credential information that is used during the Open Liberty authentication and authorization work flow. It provides a quick, simple, and convenient authentication mechanism for Liberty application testing, debugging, demos, and more. + +*New features in Security 4.0:* + +- *Multiple HTTP Authentication Mechanisms (HAMs)* can now be defined within the same application. These mechanisms can be specified through built-in Jakarta annotations such as `@FormAuthenticationMechanismDefinition` or through custom implementations of the `HttpAuthenticationMechanism` interface. + +- *New `SecurityContext` method*: A new method is added to the `SecurityContext` interface called `getAllDeclaredCallerRoles()`, which returns a list of all static (declared) application roles that the authenticated caller is in. + +- *Removed deprecated class*: References to the `IdentityStorePermission` class are removed as it was previously deprecated. + + +==== Multiple HTTP Authentication Mechanisms Example + +[source,java] +---- +import jakarta.security.enterprise.authentication.mechanism.http.HttpAuthenticationMechanism; +import jakarta.security.enterprise.authentication.mechanism.http.HttpMessageContext; +import jakarta.security.enterprise.credential.UsernamePasswordCredential; +import jakarta.security.enterprise.identitystore.CredentialValidationResult; +import jakarta.security.enterprise.identitystore.IdentityStoreHandler; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import jakarta.enterprise.context.ApplicationScoped; +import jakarta.inject.Inject; +import jakarta.security.enterprise.AuthenticationStatus; +import jakarta.security.enterprise.AuthenticationException; +import jakarta.security.enterprise.authentication.mechanism.http.FormAuthenticationMechanismDefinition; + +// Define a form-based authentication mechanism using annotation +@FormAuthenticationMechanismDefinition( + loginToContinue = @LoginToContinue( + loginPage = "/login.jsp", + errorPage = "/error.jsp" + ) +) +@ApplicationScoped +public class CustomAuthenticationMechanism implements HttpAuthenticationMechanism { + + @Inject + private IdentityStoreHandler identityStoreHandler; + + @Override + public AuthenticationStatus validateRequest( + HttpServletRequest request, + HttpServletResponse response, + HttpMessageContext context) throws AuthenticationException { + + String username = request.getParameter("username"); + String password = request.getParameter("password"); + + if (username != null && password != null) { + CredentialValidationResult result = identityStoreHandler.validate( + new UsernamePasswordCredential(username, password) + ); + + if (result.getStatus() == CredentialValidationResult.Status.VALID) { + return context.notifyContainerAboutLogin(result); + } + } + + return context.doNothing(); + } +} +---- + +==== Using getAllDeclaredCallerRoles() Example + +[source,java] +---- +import jakarta.inject.Inject; +import jakarta.security.enterprise.SecurityContext; +import jakarta.ws.rs.GET; +import jakarta.ws.rs.Path; +import jakarta.ws.rs.Produces; +import jakarta.ws.rs.core.MediaType; +import java.util.Set; + +@Path("/roles") +public class RolesResource { + + @Inject + private SecurityContext securityContext; + + @GET + @Produces(MediaType.APPLICATION_JSON) + public Set getAllRoles() { + // Get all declared roles for the authenticated caller + return securityContext.getAllDeclaredCallerRoles(); + } +} +---- + +=== Servlet 6.1 + +Jakarta Servlet 6.1 defines a server-side API for handling HTTP requests and responses. This release removes references to the SecurityManager and provides various small enhancements and clarifications. + +*New features in Servlet 6.1:* +- Allow control of status code and response body when sending a redirect +- Add a query string attribute to error dispatches +- Add constants for new HTTP status codes +- Add overloaded methods that use `Charset` rather than `String` +- Add `ByteBuffer` support to `ServletInputStream` and `ServletOutputStream` +- Various clarifications throughout the specification + +*Removals:* +- All references to the SecurityManager and associated APIs have been removed + +[source,java] +---- +import jakarta.servlet.ServletException; +import jakarta.servlet.annotation.WebServlet; +import jakarta.servlet.http.HttpServlet; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import jakarta.servlet.AsyncContext; +import java.io.IOException; +import java.io.PrintWriter; + +@WebServlet(urlPatterns = "/async", asyncSupported = true) +public class AsyncServletExample extends HttpServlet { + + @Override + protected void doGet(HttpServletRequest request, HttpServletResponse response) + throws ServletException, IOException { + + // Start async processing + AsyncContext asyncContext = request.startAsync(); + asyncContext.setTimeout(30000); // 30 seconds timeout + + // Process in separate thread + asyncContext.start(() -> { + try { + // Simulate long-running operation + String result = performLongOperation(); + + HttpServletResponse asyncResponse = + (HttpServletResponse) asyncContext.getResponse(); + asyncResponse.setContentType("application/json"); + + PrintWriter writer = asyncResponse.getWriter(); + writer.write("{\"result\": \"" + result + "\"}"); + writer.flush(); + + asyncContext.complete(); + } catch (IOException e) { + asyncContext.complete(); + } + }); + } + + private String performLongOperation() { + // Simulate processing + try { + Thread.sleep(5000); + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + } + return "Operation completed"; + } +} +---- + +=== RESTful Web Services 4.0 + +Jakarta RESTful Web Services 4.0 provides a foundational API to develop web services following the Representational State Transfer (REST) architectural pattern. The goal of this release is to remove the JAXB dependency and ManagedBean support from Jakarta RESTful Web Services and add TCK tests to fill verification gaps while maintaining backward compatibility with earlier releases. + +*New features in RESTful Web Services 4.0:* + +- *TCK tests for multipart/form-data API*: Added comprehensive tests for multipart form data handling +- *TCK tests for default ExceptionMapper*: Added tests to verify default exception mapping behavior +- *Convenience method for checking header value lists*: New method to easily check if a header contains specific values +- *Required TCK for convenience method*: Added required tests for the new convenience method merged in PR 1066 +- *Clarified JavaSE support*: Clarified JavaSE support in Section 2.3 of specification +- *Added getMatchedResourceTemplate method to UriInfo*: New method to retrieve the matched resource template +- *Added JSON Merge Patch support*: Support for RFC 7396 JSON Merge Patch + +*Removals:* +- *Remove JAXB dependency*: Jakarta REST no longer depends on JAXB +- *Remove ManagedBean support*: ManagedBean support has been removed from Jakarta REST + +==== Basic REST Resource Example + +[source,java] +---- +import jakarta.ws.rs.*; +import jakarta.ws.rs.core.*; +import java.util.List; +import java.util.ArrayList; + +@Path("/products") +@Produces(MediaType.APPLICATION_JSON) +@Consumes(MediaType.APPLICATION_JSON) +public class ProductResource { + + private static List products = new ArrayList<>(); + + @GET + public Response getAllProducts() { + return Response.ok(products).build(); + } + + @GET + @Path("/{id}") + public Response getProduct(@PathParam("id") Long id) { + Product product = products.stream() + .filter(p -> p.getId().equals(id)) + .findFirst() + .orElse(null); + + if (product == null) { + return Response.status(Response.Status.NOT_FOUND).build(); + } + + return Response.ok(product).build(); + } + + @POST + public Response createProduct(Product product) { + products.add(product); + return Response.status(Response.Status.CREATED) + .entity(product) + .build(); + } + + @PUT + @Path("/{id}") + public Response updateProduct(@PathParam("id") Long id, Product updatedProduct) { + Product product = products.stream() + .filter(p -> p.getId().equals(id)) + .findFirst() + .orElse(null); + + if (product == null) { + return Response.status(Response.Status.NOT_FOUND).build(); + } + + products.remove(product); + products.add(updatedProduct); + + return Response.ok(updatedProduct).build(); + } + + @DELETE + @Path("/{id}") + public Response deleteProduct(@PathParam("id") Long id) { + boolean removed = products.removeIf(p -> p.getId().equals(id)); + + if (!removed) { + return Response.status(Response.Status.NOT_FOUND).build(); + } + + return Response.noContent().build(); + } +} + +class Product { + private Long id; + private String name; + private Double price; + + // Constructors, getters, setters + public Long getId() { return id; } + public void setId(Long id) { this.id = id; } + public String getName() { return name; } + public void setName(String name) { this.name = name; } + public Double getPrice() { return price; } + public void setPrice(Double price) { this.price = price; } +} +---- + +==== Using getMatchedResourceTemplate (NEW in 4.0) + +[source,java] +---- +import jakarta.ws.rs.*; +import jakarta.ws.rs.core.*; +import jakarta.inject.Inject; + +@Path("/api/users") +public class UserResource { + + @Context + private UriInfo uriInfo; + + @GET + @Path("/{userId}/orders/{orderId}") + @Produces(MediaType.APPLICATION_JSON) + public Response getUserOrder( + @PathParam("userId") Long userId, + @PathParam("orderId") Long orderId) { + + // NEW in REST 4.0: getMatchedResourceTemplate method + String template = uriInfo.getMatchedResourceTemplate(); + System.out.println("Matched template: " + template); + // Output: /api/users/{userId}/orders/{orderId} + + // Use the template for logging, metrics, or routing decisions + return Response.ok() + .entity(new Order(orderId, userId)) + .header("X-Resource-Template", template) + .build(); + } +} + +class Order { + private Long orderId; + private Long userId; + + public Order(Long orderId, Long userId) { + this.orderId = orderId; + this.userId = userId; + } + + public Long getOrderId() { return orderId; } + public Long getUserId() { return userId; } +} +---- + +==== JSON Merge Patch Support (NEW in 4.0) + +[source,java] +---- +import jakarta.ws.rs.*; +import jakarta.ws.rs.core.*; +import jakarta.json.JsonMergePatch; +import jakarta.json.JsonValue; +import jakarta.json.bind.Jsonb; +import jakarta.json.bind.JsonbBuilder; + +@Path("/customers") +@Produces(MediaType.APPLICATION_JSON) +@Consumes(MediaType.APPLICATION_JSON) +public class CustomerResource { + + private static Customer customer = new Customer(1L, "John Doe", "john@example.com"); + + @GET + @Path("/{id}") + public Response getCustomer(@PathParam("id") Long id) { + return Response.ok(customer).build(); + } + + // NEW in REST 4.0: JSON Merge Patch support (RFC 7396) + @PATCH + @Path("/{id}") + @Consumes("application/merge-patch+json") + public Response patchCustomer( + @PathParam("id") Long id, + JsonMergePatch patch) { + + try (Jsonb jsonb = JsonbBuilder.create()) { + // Convert customer to JsonValue + JsonValue customerJson = jsonb.fromJson( + jsonb.toJson(customer), JsonValue.class); + + // Apply the merge patch + JsonValue patchedJson = patch.apply(customerJson); + + // Convert back to Customer object + Customer patchedCustomer = jsonb.fromJson( + patchedJson.toString(), Customer.class); + + customer = patchedCustomer; + + return Response.ok(customer).build(); + } catch (Exception e) { + return Response.status(Response.Status.BAD_REQUEST) + .entity("Invalid patch: " + e.getMessage()) + .build(); + } + } +} + +class Customer { + private Long id; + private String name; + private String email; + + public Customer() {} + + public Customer(Long id, String name, String email) { + this.id = id; + this.name = name; + this.email = email; + } + + // Getters and setters + public Long getId() { return id; } + public void setId(Long id) { this.id = id; } + public String getName() { return name; } + public void setName(String name) { this.name = name; } + public String getEmail() { return email; } + public void setEmail(String email) { this.email = email; } +} +---- + +==== Multipart Form Data Handling + +[source,java] +---- +import jakarta.ws.rs.*; +import jakarta.ws.rs.core.*; +import java.io.InputStream; +import org.glassfish.jersey.media.multipart.FormDataParam; +import org.glassfish.jersey.media.multipart.FormDataContentDisposition; + +@Path("/upload") +public class FileUploadResource { + + @POST + @Consumes(MediaType.MULTIPART_FORM_DATA) + @Produces(MediaType.APPLICATION_JSON) + public Response uploadFile( + @FormDataParam("file") InputStream fileInputStream, + @FormDataParam("file") FormDataContentDisposition fileMetaData, + @FormDataParam("description") String description) { + + String fileName = fileMetaData.getFileName(); + long fileSize = fileMetaData.getSize(); + + // Process the file + System.out.println("Uploading file: " + fileName); + System.out.println("File size: " + fileSize); + System.out.println("Description: " + description); + + // Save file logic here... + + return Response.ok() + .entity(new UploadResponse(fileName, fileSize, "Upload successful")) + .build(); + } +} + +class UploadResponse { + private String fileName; + private long fileSize; + private String message; + + public UploadResponse(String fileName, long fileSize, String message) { + this.fileName = fileName; + this.fileSize = fileSize; + this.message = message; + } + + // Getters + public String getFileName() { return fileName; } + public long getFileSize() { return fileSize; } + public String getMessage() { return message; } +} +---- + +=== Persistence 3.2 + +Jakarta Persistence 3.2 defines a standard for management of persistence and object/relational mapping in Java environments. + +*New features in Persistence 3.2:* +- *Java Record Support*: Adds support for Java record types as embeddable classes +- *java.time Support*: Adds support for `java.time.Instant` and `java.time.Year` with clarified JDBC mappings for basic types +- *New Query Operators*: Adds `union`, `intersect`, `except`, `cast`, `left`, `right`, and `replace` operators for Jakarta Persistence QL and criteria queries +- *String Concatenation*: Adds `||` as a concatenation operator and `id` and `version` functions to Jakarta Persistence QL +- *Criteria API Enhancements*: Adds `CriteriaSelect`, `subquery(EntityType)` and joins on `EntityType` to Criteria API +- *Null Precedence*: Adds support for specifying null precedence when ordering Jakarta Persistence QL and criteria queries +- *Query Methods*: Adds `getSingleResultOrNull()` to `Query`, `TypedQuery`, `StoredProcedureQuery` +- *Named Queries*: Adds `entities()`, `classes()` and `columns()` to `NamedNativeQuery` +- *Lock Mode*: Adds `lockMode()` to `EntityResult` with the default being `OPTIMISTIC` + +[source,java] +---- +import jakarta.persistence.*; +import java.time.Instant; +import java.time.Year; +import java.util.List; + +// NEW in 3.2: Java Record as Embeddable +@Embeddable +public record Address( + String street, + String city, + String state, + String zipCode +) {} + +@Entity +@Table(name = "customers") +@NamedQuery(name = "Customer.findByEmail", + query = "SELECT c FROM Customer c WHERE c.email = :email") +public class Customer { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + @Column(nullable = false) + private String name; + + @Column(unique = true, nullable = false) + private String email; + + // NEW in 3.2: Embedded record type + @Embedded + private Address address; + + // NEW in 3.2: java.time.Instant support + private Instant createdAt; + + // NEW in 3.2: java.time.Year support + private Year memberSince; + + @OneToMany(mappedBy = "customer", cascade = CascadeType.ALL) + private List orders; + + // Constructors, getters, setters +} + +@Stateless +public class CustomerRepository { + + @PersistenceContext + private EntityManager em; + + public Customer findById(Long id) { + return em.find(Customer.class, id); + } + + public Customer findByEmail(String email) { + return em.createNamedQuery("Customer.findByEmail", Customer.class) + .setParameter("email", email) + .getSingleResult(); + } + + // NEW in 3.2: getSingleResultOrNull() method + public Customer findByEmailOrNull(String email) { + return em.createQuery("SELECT c FROM Customer c WHERE c.email = :email", Customer.class) + .setParameter("email", email) + .getSingleResultOrNull(); // Returns null instead of throwing exception + } + + // NEW in 3.2: String concatenation with || operator + public List getFullNames() { + return em.createQuery( + "SELECT c.name || ' (' || c.email || ')' FROM Customer c", + String.class + ).getResultList(); + } + + // NEW in 3.2: UNION operator + public List findActiveAndVIPCustomers() { + return em.createQuery( + "SELECT c FROM Customer c WHERE c.status = 'ACTIVE' " + + "UNION " + + "SELECT c FROM Customer c WHERE c.vipLevel > 5", + Customer.class + ).getResultList(); + } + + // NEW in 3.2: Null precedence in ordering + public List findCustomersOrderedByCity() { + return em.createQuery( + "SELECT c FROM Customer c ORDER BY c.address.city NULLS LAST", + Customer.class + ).getResultList(); + } + + public List findCustomersWithOrders() { + // Using JOIN FETCH for efficient loading + return em.createQuery( + "SELECT DISTINCT c FROM Customer c LEFT JOIN FETCH c.orders", + Customer.class + ).getResultList(); + } + + public void save(Customer customer) { + if (customer.getId() == null) { + em.persist(customer); + } else { + em.merge(customer); + } + } +} +---- + +=== WebSocket 2.2 + +Jakarta WebSocket 2.2 defines an API for Server and Client Endpoints for the WebSocket protocol (RFC6455). This release removes references to the SecurityManager and provides some minor updates and clarifications. + +*New features in WebSocket 2.2:* + +- *Clarified ping/pong responsibilities*: The specification now clearly defines the responsibilities for sending ping and pong messages between client and server +- *New `getSession()` method*: Added `getSession()` method to `SendResult` class to retrieve the session associated with a send operation +- *Clarified `maxMessageSize` behavior*: Clarified the behavior when `@OnMessage.maxMessageSize` is set to a value larger than `Integer.MAX_VALUE` + +*Removals:* +- All references to the SecurityManager have been removed + +==== Using the new getSession() method in SendResult + +The new `getSession()` method allows you to retrieve the WebSocket session associated with a send operation, which is particularly useful when handling asynchronous message sending: + +[source,java] +---- +import jakarta.websocket.*; +import jakarta.websocket.server.ServerEndpoint; +import jakarta.websocket.server.PathParam; +import java.io.IOException; +import java.util.Set; +import java.util.concurrent.CopyOnWriteArraySet; +import java.util.concurrent.Future; + +@ServerEndpoint("/notifications/{userId}") +public class NotificationWebSocket { + + private static final Set sessions = new CopyOnWriteArraySet<>(); + + @OnOpen + public void onOpen(Session session, @PathParam("userId") String userId) { + sessions.add(session); + session.getUserProperties().put("userId", userId); + System.out.println("User " + userId + " connected"); + } + + @OnMessage + public void onMessage(String message, Session session) { + String userId = (String) session.getUserProperties().get("userId"); + System.out.println("Received message from " + userId + ": " + message); + + // Echo the message back + sendAsyncMessage(session, "Echo: " + message); + } + + @OnClose + public void onClose(Session session, @PathParam("userId") String userId) { + sessions.remove(session); + System.out.println("User " + userId + " disconnected"); + } + + @OnError + public void onError(Session session, Throwable throwable) { + System.err.println("WebSocket error: " + throwable.getMessage()); + throwable.printStackTrace(); + } + + // Demonstrates the new getSession() method in SendResult + private void sendAsyncMessage(Session session, String message) { + try { + RemoteEndpoint.Async asyncRemote = session.getAsyncRemote(); + Future future = asyncRemote.sendText(message); + + // Add a callback to handle the send result + future.get(); // Wait for completion + + // In a real application, you might use a SendHandler instead + asyncRemote.sendText(message, new SendHandler() { + @Override + public void onResult(SendResult result) { + // NEW in WebSocket 2.2: getSession() method + Session resultSession = result.getSession(); + + if (result.isOK()) { + String userId = (String) resultSession.getUserProperties().get("userId"); + System.out.println("Message sent successfully to user: " + userId); + } else { + System.err.println("Failed to send message to session: " + + resultSession.getId()); + System.err.println("Error: " + result.getException().getMessage()); + } + } + }); + } catch (Exception e) { + System.err.println("Error sending async message: " + e.getMessage()); + } + } + + // Broadcast message to all connected sessions + public void broadcastToAll(String message) { + sessions.forEach(session -> { + if (session.isOpen()) { + session.getAsyncRemote().sendText(message, new SendHandler() { + @Override + public void onResult(SendResult result) { + // Use the new getSession() method to identify which session + Session resultSession = result.getSession(); + if (!result.isOK()) { + System.err.println("Failed to broadcast to session: " + + resultSession.getId()); + } + } + }); + } + }); + } +} +---- + +==== Ping/Pong Message Handling + +WebSocket 2.2 clarifies the responsibilities for ping and pong messages: + +[source,java] +---- +import jakarta.websocket.*; +import jakarta.websocket.server.ServerEndpoint; +import java.io.IOException; +import java.nio.ByteBuffer; + +@ServerEndpoint("/ping-pong") +public class PingPongWebSocket { + + @OnOpen + public void onOpen(Session session) { + System.out.println("WebSocket opened: " + session.getId()); + + // Send a ping message to the client + try { + session.getBasicRemote().sendPing(ByteBuffer.wrap("ping".getBytes())); + } catch (IOException e) { + e.printStackTrace(); + } + } + + @OnMessage + public void onPongMessage(PongMessage pong, Session session) { + // Handle pong messages received from the client + ByteBuffer data = pong.getApplicationData(); + byte[] bytes = new byte[data.remaining()]; + data.get(bytes); + String pongData = new String(bytes); + + System.out.println("Received pong from " + session.getId() + ": " + pongData); + } + + @OnMessage + public void onTextMessage(String message, Session session) { + System.out.println("Received text message: " + message); + + // Send a pong message in response + try { + session.getBasicRemote().sendPong(ByteBuffer.wrap("pong".getBytes())); + } catch (IOException e) { + e.printStackTrace(); + } + } + + @OnClose + public void onClose(Session session) { + System.out.println("WebSocket closed: " + session.getId()); + } + + @OnError + public void onError(Session session, Throwable throwable) { + System.err.println("WebSocket error: " + throwable.getMessage()); + } +} +---- + +=== Validation 3.1 + +Jakarta Validation 3.1 defines a metadata model and API for JavaBean and method validation. This release is targeting Jakarta EE 11 and has clarified support for Records introduced by JEP 395. + +*Key changes in Validation 3.1:* +- Clarify Java Records support for validation +- Update dependencies for Jakarta EE 11 +- No removals, deprecations, or backwards incompatible changes + +[source,java] +---- +import jakarta.validation.constraints.*; +import jakarta.validation.Constraint; +import jakarta.validation.ConstraintValidator; +import jakarta.validation.ConstraintValidatorContext; +import jakarta.validation.Payload; +import jakarta.validation.Valid; +import java.lang.annotation.*; + +// NEW in 3.1: Java Record with validation support +public record UserProfile( + @NotNull(message = "Username is required") + @Size(min = 3, max = 20, message = "Username must be between 3 and 20 characters") + @Pattern(regexp = "^[a-zA-Z0-9_]+$", message = "Username can only contain letters, numbers, and underscores") + String username, + + @NotNull(message = "Email is required") + @Email(message = "Invalid email format") + String email, + + @NotNull(message = "Age is required") + @Min(value = 18, message = "Must be at least 18 years old") + @Max(value = 120, message = "Age must be realistic") + Integer age +) {} + +// Traditional class with validation +public class UserRegistration { + + @NotNull(message = "Username is required") + @Size(min = 3, max = 20, message = "Username must be between 3 and 20 characters") + @Pattern(regexp = "^[a-zA-Z0-9_]+$", message = "Username can only contain letters, numbers, and underscores") + private String username; + + @NotNull(message = "Email is required") + @Email(message = "Invalid email format") + private String email; + + @NotNull(message = "Password is required") + @Size(min = 8, message = "Password must be at least 8 characters") + @StrongPassword + private String password; + + @NotNull(message = "Age is required") + @Min(value = 18, message = "Must be at least 18 years old") + @Max(value = 120, message = "Age must be realistic") + private Integer age; + + @Future(message = "Subscription end date must be in the future") + private LocalDate subscriptionEndDate; + + // Getters and setters +} + +// Custom validation annotation +@Target({ElementType.FIELD, ElementType.PARAMETER}) +@Retention(RetentionPolicy.RUNTIME) +@Constraint(validatedBy = StrongPasswordValidator.class) +@Documented +public @interface StrongPassword { + String message() default "Password must contain at least one uppercase letter, one lowercase letter, one digit, and one special character"; + Class[] groups() default {}; + Class[] payload() default {}; +} + +// Custom validator implementation +public class StrongPasswordValidator + implements ConstraintValidator { + + @Override + public boolean isValid(String password, ConstraintValidatorContext context) { + if (password == null) { + return false; + } + + boolean hasUppercase = password.chars().anyMatch(Character::isUpperCase); + boolean hasLowercase = password.chars().anyMatch(Character::isLowerCase); + boolean hasDigit = password.chars().anyMatch(Character::isDigit); + boolean hasSpecial = password.chars() + .anyMatch(ch -> "!@#$%^&*()_+-=[]{}|;:,.<>?".indexOf(ch) >= 0); + + return hasUppercase && hasLowercase && hasDigit && hasSpecial; + } +} + +// Using validation in a REST endpoint +@Path("/users") +public class UserResource { + + @Inject + private Validator validator; + + @POST + @Path("/register") + @Consumes(MediaType.APPLICATION_JSON) + public Response registerUser(@Valid UserRegistration user) { + // Validation happens automatically via @Valid + // If validation fails, a ConstraintViolationException is thrown + + // Process registration + return Response.status(Response.Status.CREATED) + .entity("User registered successfully") + .build(); + } + + // Manual validation example + @POST + @Path("/validate") + @Consumes(MediaType.APPLICATION_JSON) + public Response validateUser(UserRegistration user) { + Set> violations = + validator.validate(user); + + if (!violations.isEmpty()) { + List errors = violations.stream() + .map(ConstraintViolation::getMessage) + .collect(Collectors.toList()); + + return Response.status(Response.Status.BAD_REQUEST) + .entity(errors) + .build(); + } + + return Response.ok("Validation passed").build(); + } +} +---- + +== Getting Started with Jakarta EE 11 on Open Liberty + +To use Jakarta EE 11 features in Open Liberty 26.0.0.5, you can enable the Jakarta EE 11 Platform feature in your `server.xml`: + +[source,xml] +---- + + + jakartaee-11.0 + + + + + + + + + + + + +---- + + +You can also enable individual features as needed: + +[source,xml] +---- + + servlet-6.1 + cdi-4.1 + persistence-3.2 + faces-4.1 + data-1.0 + websocket-2.2 + validation-3.1 + +---- + +=== Maven Dependencies + +Add Jakarta EE 11 dependencies to your `pom.xml`: + +[source,xml] +---- + + + + jakarta.platform + jakarta.jakartaee-api + 11.0.0 + provided + + + + + jakarta.data + jakarta.data-api + 1.0.0 + provided + + +---- + +== Summary of Key Enhancements by Specification + +Based on the official Jakarta EE 11 specifications: + +=== Jakarta Data 1.0 (NEW) +- Repository pattern for data access +- Query by method name +- Pagination support +- Platform integrations with CDI, Persistence, NoSQL, Transactions, and Validation +- Multiple profile support (standalone, core, web, platform) + +=== CDI 4.1 +- Breaking up spec/TCK to remove circular dependencies +- Method invokers and executable methods +- Getting interceptor bindings in standard way +- @Priority on producers +- Programmatic access to Assignability rules + +=== Authentication 3.1 +- Removes references to the SecurityManager +- Evolves the API to support Jakarta Security goals +- Consists of several profiles for different containers (Jakarta Servlet, etc.) +- Low-level SPI for authentication mechanisms + +=== Servlet 6.1 +- Control of status code and response body when sending redirects +- Query string attribute to error dispatches +- New HTTP status code constants +- Charset-based overloaded methods +- ByteBuffer support for ServletInputStream and ServletOutputStream +- *Removed*: All SecurityManager references + +=== Persistence 3.2 +- Java record types as embeddable classes +- java.time.Instant and java.time.Year support +- New query operators: union, intersect, except, cast, left, right, replace +- String concatenation operator (||) +- Null precedence in ordering (NULLS FIRST/LAST) +- getSingleResultOrNull() method +- CriteriaSelect and enhanced Criteria API + +=== Validation 3.1 +- Clarified support for Java Records +- Updated dependencies for Jakarta EE 11 +- No removals, deprecations, or backwards incompatible changes + +== Migration Considerations + +When migrating from Jakarta EE 10 to Jakarta EE 11, review the updated specifications to understand the changes and enhancements. Most applications should migrate smoothly, but it's important to test thoroughly, especially if you're using features that have been updated. + +Key areas to review: +- *Jakarta Data 1.0*: Consider migrating existing DAO/repository code to Jakarta Data for improved productivity and reduced boilerplate +- *Authentication 3.1*: Remove any SecurityManager dependencies from authentication modules +- *CDI 4.1*: Review dependency injection configurations, especially if using custom interceptors or producers +- *Servlet 6.1*: Remove any SecurityManager dependencies; test redirect handling and error dispatches +- *Persistence 3.2*: Leverage Java records for embeddable types; update queries to use new operators; test java.time types +- *Validation 3.1*: Leverage Java Records for validated data structures +- *Concurrency 3.1*: Test async operations, especially if using Java 21 Virtual Threads + +== Performance Benefits with Java 21 + +Jakarta EE 11's support for Java 21 brings significant performance improvements: + +[source,java] +---- +// Virtual Threads example with Jakarta Concurrency +@ApplicationScoped +public class VirtualThreadExample { + + @Resource + private ManagedExecutorService executor; + + public void processLargeDataset(List dataset) { + // Each task runs on a virtual thread + List> futures = dataset.stream() + .map(data -> CompletableFuture.supplyAsync( + () -> processData(data), + executor + )) + .toList(); + + // Wait for all to complete + CompletableFuture.allOf(futures.toArray(new CompletableFuture[0])) + .join(); + } + + private Result processData(Data data) { + // Processing logic + return new Result(); + } +} +---- + +== Conclusion + +Jakarta EE 11 in Open Liberty 26.0.0.5 represents a significant advancement in enterprise Java development. The introduction of Jakarta Data 1.0 alone is a game-changer, dramatically reducing boilerplate code and improving developer productivity. Combined with updates across all major specifications, enhanced performance through Java 21 support, and a comprehensive set of modern APIs, Jakarta EE 11 provides a solid foundation for building cloud-native enterprise applications. + +The combination of Open Liberty's lightweight, fast runtime and Jakarta EE 11's powerful features makes this an excellent choice for organizations looking to modernize their enterprise Java applications while maintaining compatibility with industry standards. + +== Learn More + +- link:https://jakarta.ee/specifications/platform/11/[Jakarta EE 11 Specifications] +- link:https://jakarta.ee/specifications/data/1.0/[Jakarta Data Specification] +- link:https://openliberty.io/docs/[Open Liberty Documentation] +- link:https://jakarta.ee/community/[Jakarta EE Community] +- link:https://github.com/OpenLiberty/open-liberty[Open Liberty GitHub] + +--- + +*Open Liberty is an open-source, lightweight, and fast Java runtime that implements Jakarta EE and MicroProfile specifications. It's designed for cloud-native applications and provides a flexible, modular architecture that allows you to use only the features you need.* + +// // // // // // // // +// LINKS +// +// OpenLiberty.io site links: +// link:/guides/microprofile-rest-client.html[Consuming RESTful Java microservices] +// +// Off-site links: +// link:https://openapi-generator.tech/docs/installation#jar[Download Instructions] +// +// // // // // // // // From 422ac5685486ba850ac242ff2744f90f20abfad2 Mon Sep 17 00:00:00 2001 From: emijiang Date: Thu, 7 May 2026 09:11:27 +0100 Subject: [PATCH 02/26] update the blog --- ...6-05-16-Jakarta EE 11 in Open Liberty.adoc | 65 ++++++++----------- 1 file changed, 27 insertions(+), 38 deletions(-) diff --git a/posts/2026-05-16-Jakarta EE 11 in Open Liberty.adoc b/posts/2026-05-16-Jakarta EE 11 in Open Liberty.adoc index 50b6603dd..e2ddb0e9c 100644 --- a/posts/2026-05-16-Jakarta EE 11 in Open Liberty.adoc +++ b/posts/2026-05-16-Jakarta EE 11 in Open Liberty.adoc @@ -11,62 +11,34 @@ blog_description: Discover Jakarta EE 11's revolutionary features in Open Libert open-graph-image: https://openliberty.io/img/twitter_card.jpg open-graph-image-alt: Open Liberty Logo --- -= TITLE += Jakarta EE 11 from Newbie to Pro with Open Liberty Emily Jiang :imagesdir: / :url-prefix: :url-about: / -//Blank line here is necessary before starting the body of the post. - -// // // // // // // // -// In the preceding section: -// Do not insert any blank lines between any of the lines. -// -// "open-graph-image" is set to OL logo. Whenever possible update this to a more appropriate/specific image (For example if present a image that is being used in the post). However, it -// can be left empty which will set it to the default -// -// "open-graph-image-alt" is a description of what is in the image (not a caption). When changing "open-graph-image" to -// a custom picture, you must provide a custom string for "open-graph-image-alt". -// -// Replace TITLE with the blog post title. -// Replace AUTHOR_NAME with your name as first author. -// Replace GITHUB_USERNAME with your GitHub username eg: lauracowen -// Replace DESCRIPTION with a short summary (~60 words) of the release (a more succinct version of the first paragraph of the post). -// -// Replace AUTHOR_NAME with your name as you'd like it to be displayed, eg: Laura Cowen -// -// Example post: 2020-04-02-generate-microprofile-rest-client-code.adoc -// -// If adding image into the post add : -// ------------------------- -// [.img_border_light] -// image::img/blog/FILE_NAME[IMAGE CAPTION ,width=70%,align="center"] -// ------------------------- -// "[.img_border_light]" = This adds a faint grey border around the image to make its edges sharper. Use it around screenshots but not -// around diagrams. Then double check how it looks. -// There is also a "[.img_border_dark]" class which tends to work best with screenshots that are taken on dark backgrounds. -// Change "FILE_NAME" to the name of the image file. Also make sure to put the image into the right folder which is: img/blog -// change the "IMAGE CAPTION" to a couple words of what the image is -// // // // // // // // +:toc: +:toc-title: Table of Contents +:toclevels: 3 link:https://jakarta.ee/release/11/[Jakarta EE 11] marks a significant milestone in the evolution of enterprise Java development. This release delivers enhanced developer productivity, improved performance, and modernized APIs that align with the Java LTS releases: Java SE 17, Java SE 21. == What's New in Jakarta EE 11? Jakarta EE 11 represents a major step forward for cloud-native enterprise Java applications. This release includes modernizing and restructuring the Test Compatibility Kits (TCKs), the new Jakarta Data specification, major updates to existing specifications, and support for the latest Java LTS release, which enables developers to leverage enhancements in Java 21, including Virtual Threads, Records. Full lists are: + * <> * <> * <> * <> * <> * <> -*<> -* <> -* <> +* <> +* <> +* <> * <> * <> * <> -* <> +* <> [#data-1.0] @@ -581,11 +553,13 @@ CDI 4.1 provides a standardized way to access interceptor binding information: [source,java] ---- import jakarta.enterprise.inject.spi.BeanManager; -import jakarta.enterprise.inject.spi.InterceptorBinding; import jakarta.inject.Inject; import jakarta.interceptor.InterceptorBinding; +import jakarta.enterprise.context.ApplicationScoped; import java.lang.annotation.Retention; import java.lang.annotation.Target; +import java.lang.annotation.Annotation; +import java.util.Set; import static java.lang.annotation.ElementType.*; import static java.lang.annotation.RetentionPolicy.RUNTIME; @@ -1106,6 +1080,7 @@ public class RolesResource { } ---- +[#servlet-6.1] === Servlet 6.1 Jakarta Servlet 6.1 defines a server-side API for handling HTTP requests and responses. This release removes references to the SecurityManager and provides various small enhancements and clarifications. @@ -1176,6 +1151,7 @@ public class AsyncServletExample extends HttpServlet { } ---- +[#restful-web-services-4.0] === RESTful Web Services 4.0 Jakarta RESTful Web Services 4.0 provides a foundational API to develop web services following the Representational State Transfer (REST) architectural pattern. The goal of this release is to remove the JAXB dependency and ManagedBean support from Jakarta RESTful Web Services and add TCK tests to fill verification gaps while maintaining backward compatibility with earlier releases. @@ -1465,6 +1441,7 @@ class UploadResponse { } ---- +[#persistence-3.2] === Persistence 3.2 Jakarta Persistence 3.2 defines a standard for management of persistence and object/relational mapping in Java environments. @@ -1595,6 +1572,7 @@ public class CustomerRepository { } ---- +[#websocket-2.2] === WebSocket 2.2 Jakarta WebSocket 2.2 defines an API for Server and Client Endpoints for the WebSocket protocol (RFC6455). This release removes references to the SecurityManager and provides some minor updates and clarifications. @@ -1768,6 +1746,7 @@ public class PingPongWebSocket { } ---- +[#validation-3.1] === Validation 3.1 Jakarta Validation 3.1 defines a metadata model and API for JavaBean and method validation. This release is targeting Jakarta EE 11 and has clarified support for Records introduced by JEP 395. @@ -1785,7 +1764,17 @@ import jakarta.validation.ConstraintValidator; import jakarta.validation.ConstraintValidatorContext; import jakarta.validation.Payload; import jakarta.validation.Valid; +import jakarta.validation.Validator; +import jakarta.validation.ConstraintViolation; +import jakarta.inject.Inject; +import jakarta.ws.rs.*; +import jakarta.ws.rs.core.MediaType; +import jakarta.ws.rs.core.Response; import java.lang.annotation.*; +import java.time.LocalDate; +import java.util.List; +import java.util.Set; +import java.util.stream.Collectors; // NEW in 3.1: Java Record with validation support public record UserProfile( From 408f6c172bf4e41ae4bd4770347ed770045ffc17 Mon Sep 17 00:00:00 2001 From: emijiang Date: Thu, 7 May 2026 09:32:58 +0100 Subject: [PATCH 03/26] update the blog to address comments --- ...6-05-16-Jakarta EE 11 in Open Liberty.adoc | 250 +++++++++++------- 1 file changed, 161 insertions(+), 89 deletions(-) diff --git a/posts/2026-05-16-Jakarta EE 11 in Open Liberty.adoc b/posts/2026-05-16-Jakarta EE 11 in Open Liberty.adoc index e2ddb0e9c..47531a08a 100644 --- a/posts/2026-05-16-Jakarta EE 11 in Open Liberty.adoc +++ b/posts/2026-05-16-Jakarta EE 11 in Open Liberty.adoc @@ -20,7 +20,7 @@ Emily Jiang :toc-title: Table of Contents :toclevels: 3 -link:https://jakarta.ee/release/11/[Jakarta EE 11] marks a significant milestone in the evolution of enterprise Java development. This release delivers enhanced developer productivity, improved performance, and modernized APIs that align with the Java LTS releases: Java SE 17, Java SE 21. +link:https://jakarta.ee/release/11/[Jakarta EE 11] marks a significant milestone in the evolution of enterprise Java development. This release delivers enhanced developer productivity, improved performance, and modernized APIs that align with the Java LTS releases: Java SE 17, Java SE 21. For more information, see the https://jakarta.ee/specifications/platform/11/[Jakarta EE Platform 11 specification] and https://jakarta.ee/specifications/pages/4.0/[Jakarta Pages 4.0 specification]. == What's New in Jakarta EE 11? @@ -67,8 +67,8 @@ Jakarta Data 1.0 includes: [source,java] ---- -import jakarta.data.Entity; -import jakarta.data.Id; +import jakarta.persistence.Entity; +import jakarta.persistence.Id; @Entity public class Product { @@ -100,8 +100,9 @@ import jakarta.data.repository.Find; import jakarta.data.repository.Query; import jakarta.data.repository.Save; import jakarta.data.repository.Delete; -import jakarta.data.repository.Page; -import jakarta.data.repository.Pageable; +import jakarta.data.repository.OrderBy; +import jakarta.data.page.Page; +import jakarta.data.page.PageRequest; import java.util.List; import java.util.Optional; @@ -125,10 +126,11 @@ public interface ProductRepository { List findByPriceBetween(double minPrice, double maxPrice); - List findByNameContaining(String keyword); + List findByNameContains(String keyword); - // Sorting and pagination - Page findByPriceGreaterThan(double price, Pageable pageable); + // Sorting and pagination - requires @OrderBy for pagination + @OrderBy("price") + Page findByPriceGreaterThan(double price, PageRequest pageRequest); // Custom queries using @Query annotation @Query("SELECT p FROM Product p WHERE p.stockQuantity < 10") @@ -139,42 +141,6 @@ public interface ProductRepository { } ---- -==== Using the Repository in a Service - -[source,java] ----- -import jakarta.enterprise.context.ApplicationScoped; -import jakarta.inject.Inject; -import jakarta.data.repository.Pageable; -import jakarta.data.repository.Page; -import java.util.List; - -@ApplicationScoped -public class ProductService { - - @Inject - private ProductRepository productRepository; - - public Product createProduct(String name, double price) { - Product product = new Product(name, price); - return productRepository.save(product); - } - - public List searchProducts(String keyword) { - return productRepository.findByNameContaining(keyword); - } - - public Page getExpensiveProducts(double minPrice, int pageNumber, int pageSize) { - Pageable pageable = Pageable.ofSize(pageSize).page(pageNumber); - return productRepository.findByPriceGreaterThan(minPrice, pageable); - } - - public List getLowStockProducts() { - return productRepository.findLowStockProducts(); - } -} ----- - ==== REST Endpoint Using Jakarta Data [source,java] @@ -183,7 +149,8 @@ import jakarta.ws.rs.*; import jakarta.ws.rs.core.MediaType; import jakarta.ws.rs.core.Response; import jakarta.inject.Inject; -import jakarta.data.repository.Page; +import jakarta.data.page.Page; +import jakarta.data.page.PageRequest; import java.util.List; @Path("/products") @@ -192,36 +159,34 @@ import java.util.List; public class ProductResource { @Inject - private ProductService productService; + private ProductRepository productRepository; @POST public Response createProduct(Product product) { - Product created = productService.createProduct( - product.getName(), - product.getPrice() - ); + Product created = productRepository.save(product); return Response.status(Response.Status.CREATED).entity(created).build(); } @GET @Path("/search") public List searchProducts(@QueryParam("keyword") String keyword) { - return productService.searchProducts(keyword); + return productRepository.findByNameContains(keyword); } @GET @Path("/expensive") public Page getExpensiveProducts( @QueryParam("minPrice") @DefaultValue("100.0") double minPrice, - @QueryParam("page") @DefaultValue("0") int page, + @QueryParam("page") @DefaultValue("1") int page, @QueryParam("size") @DefaultValue("20") int size) { - return productService.getExpensiveProducts(minPrice, page, size); + PageRequest pageRequest = PageRequest.ofPage(page).size(size); + return productRepository.findByPriceGreaterThan(minPrice, pageRequest); } @GET @Path("/low-stock") public List getLowStockProducts() { - return productService.getLowStockProducts(); + return productRepository.findLowStockProducts(); } } ---- @@ -413,13 +378,20 @@ Enhanced concurrency utilities with better Virtual Thread support for Java 21+. ---- import jakarta.enterprise.concurrent.ManagedExecutorService; import jakarta.enterprise.concurrent.ManagedScheduledExecutorService; +import jakarta.enterprise.concurrent.ManagedExecutorDefinition; import jakarta.annotation.Resource; +import jakarta.enterprise.context.ApplicationScoped; import java.util.concurrent.CompletableFuture; import java.util.concurrent.TimeUnit; +@ApplicationScoped +@ManagedExecutorDefinition( + name = "java:app/concurrent/VirtualThreadExecutor", + virtual = true +) public class ConcurrencyExample { - @Resource + @Resource(lookup = "java:app/concurrent/VirtualThreadExecutor") private ManagedExecutorService executorService; @Resource @@ -427,10 +399,10 @@ public class ConcurrencyExample { public CompletableFuture asyncOperation() { // Leverages Virtual Threads on Java 21+ - return CompletableFuture.supplyAsync(() -> { + return executorService.supplyAsync(() -> { // Long-running operation return processData(); - }, executorService); + }); } public void scheduleTask() { @@ -1096,6 +1068,46 @@ Jakarta Servlet 6.1 defines a server-side API for handling HTTP requests and res *Removals:* - All references to the SecurityManager and associated APIs have been removed +==== Custom Redirect with Status Code Control + +Servlet 6.1 allows you to control the HTTP status code when sending redirects: + +[source,java] +---- +import jakarta.servlet.ServletException; +import jakarta.servlet.annotation.WebServlet; +import jakarta.servlet.http.HttpServlet; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import java.io.IOException; + +@WebServlet("/redirect-example") +public class RedirectServlet extends HttpServlet { + + @Override + protected void doGet(HttpServletRequest request, HttpServletResponse response) + throws ServletException, IOException { + + String action = request.getParameter("action"); + + if ("permanent".equals(action)) { + // Send a 301 Moved Permanently redirect + response.sendRedirect("/new-location", HttpServletResponse.SC_MOVED_PERMANENTLY); + } else if ("temporary".equals(action)) { + // Send a 307 Temporary Redirect (preserves request method) + response.sendRedirect("/temp-location", HttpServletResponse.SC_TEMPORARY_REDIRECT); + } else { + // Default 302 Found redirect + response.sendRedirect("/default-location"); + } + } +} +---- + +==== Using Charset Methods + +Servlet 6.1 adds overloaded methods that accept `Charset` instead of `String` for better type safety: + [source,java] ---- import jakarta.servlet.ServletException; @@ -1103,50 +1115,110 @@ import jakarta.servlet.annotation.WebServlet; import jakarta.servlet.http.HttpServlet; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; -import jakarta.servlet.AsyncContext; import java.io.IOException; import java.io.PrintWriter; +import java.nio.charset.StandardCharsets; -@WebServlet(urlPatterns = "/async", asyncSupported = true) -public class AsyncServletExample extends HttpServlet { +@WebServlet("/charset-example") +public class CharsetServlet extends HttpServlet { @Override protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { - // Start async processing - AsyncContext asyncContext = request.startAsync(); - asyncContext.setTimeout(30000); // 30 seconds timeout + // Use Charset instead of String for character encoding + response.setCharacterEncoding(StandardCharsets.UTF_8); + response.setContentType("text/html"); - // Process in separate thread - asyncContext.start(() -> { - try { - // Simulate long-running operation - String result = performLongOperation(); - - HttpServletResponse asyncResponse = - (HttpServletResponse) asyncContext.getResponse(); - asyncResponse.setContentType("application/json"); - - PrintWriter writer = asyncResponse.getWriter(); - writer.write("{\"result\": \"" + result + "\"}"); - writer.flush(); - - asyncContext.complete(); - } catch (IOException e) { - asyncContext.complete(); - } - }); + PrintWriter writer = response.getWriter(); + writer.println(""); + writer.println("

UTF-8 Encoded Response

"); + writer.println("

Special characters: é, ñ, ü, 中文

"); + writer.println(""); } +} +---- + +==== ByteBuffer Support + +Servlet 6.1 adds `ByteBuffer` support to `ServletInputStream` and `ServletOutputStream`: + +[source,java] +---- +import jakarta.servlet.ServletException; +import jakarta.servlet.annotation.WebServlet; +import jakarta.servlet.http.HttpServlet; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import jakarta.servlet.ServletOutputStream; +import java.io.IOException; +import java.nio.ByteBuffer; +import java.nio.charset.StandardCharsets; + +@WebServlet("/bytebuffer-example") +public class ByteBufferServlet extends HttpServlet { - private String performLongOperation() { - // Simulate processing - try { - Thread.sleep(5000); - } catch (InterruptedException e) { - Thread.currentThread().interrupt(); + @Override + protected void doGet(HttpServletRequest request, HttpServletResponse response) + throws ServletException, IOException { + + response.setContentType("application/octet-stream"); + + // Create a ByteBuffer with data + String data = "Binary data using ByteBuffer"; + ByteBuffer buffer = ByteBuffer.wrap(data.getBytes(StandardCharsets.UTF_8)); + + // Write ByteBuffer directly to ServletOutputStream + ServletOutputStream outputStream = response.getOutputStream(); + outputStream.write(buffer); + outputStream.flush(); + } +} +---- + +==== Error Dispatch with Query String + +Servlet 6.1 adds a query string attribute to error dispatches: + +[source,java] +---- +import jakarta.servlet.ServletException; +import jakarta.servlet.annotation.WebServlet; +import jakarta.servlet.http.HttpServlet; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import jakarta.servlet.RequestDispatcher; +import java.io.IOException; +import java.io.PrintWriter; + +@WebServlet("/error-handler") +public class ErrorHandlerServlet extends HttpServlet { + + @Override + protected void doGet(HttpServletRequest request, HttpServletResponse response) + throws ServletException, IOException { + + response.setContentType("text/html"); + PrintWriter writer = response.getWriter(); + + // Access error attributes including the new query string attribute + Integer statusCode = (Integer) request.getAttribute(RequestDispatcher.ERROR_STATUS_CODE); + String message = (String) request.getAttribute(RequestDispatcher.ERROR_MESSAGE); + String requestUri = (String) request.getAttribute(RequestDispatcher.ERROR_REQUEST_URI); + String queryString = (String) request.getAttribute(RequestDispatcher.ERROR_QUERY_STRING); + + writer.println(""); + writer.println("

Error Handler

"); + writer.println("

Status Code: " + statusCode + "

"); + writer.println("

Message: " + message + "

"); + writer.println("

Request URI: " + requestUri + "

"); + + // New in Servlet 6.1: Query string is now available in error dispatches + if (queryString != null) { + writer.println("

Query String: " + queryString + "

"); } - return "Operation completed"; + + writer.println(""); } } ---- From 244a94ac3cdf82f7d83d28b81393e34b0de353a4 Mon Sep 17 00:00:00 2001 From: emijiang Date: Thu, 7 May 2026 09:58:45 +0100 Subject: [PATCH 04/26] address reviews --- .../2026-05-16-Jakarta EE 11 in Open Liberty.adoc | 14 +++----------- 1 file changed, 3 insertions(+), 11 deletions(-) diff --git a/posts/2026-05-16-Jakarta EE 11 in Open Liberty.adoc b/posts/2026-05-16-Jakarta EE 11 in Open Liberty.adoc index 47531a08a..47e5632bd 100644 --- a/posts/2026-05-16-Jakarta EE 11 in Open Liberty.adoc +++ b/posts/2026-05-16-Jakarta EE 11 in Open Liberty.adoc @@ -48,7 +48,7 @@ One of the most exciting additions to Jakarta EE 11 is *Jakarta Data 1.0*, a new === What is Jakarta Data? -Jakarta Data provides an API for easier data access. A Java developer can split the persistence from the model with several features, such as the ability to compose custom query methods or a Repository interface. Jakarta Data's goal is to provide a familiar and consistent, Jakarta-based programming model for data access while still retaining the particular traits of the underlying data store. +Jakarta Data provides an API for easier data access. A Java developer can split the details of persistence from the data model with several features, such as the ability to compose custom query methods on a Repository interface. Jakarta Data's goal is to provide a familiar and consistent, Jakarta-based programming model for data access while still retaining the particular traits of the underlying data store. It is designed to be flexible and extensible, allowing developers to use it with various types of databases, including relational databases, NoSQL databases, and even in-memory data stores. === Key Features of Jakarta Data 1.0 @@ -386,12 +386,12 @@ import java.util.concurrent.TimeUnit; @ApplicationScoped @ManagedExecutorDefinition( - name = "java:app/concurrent/VirtualThreadExecutor", + name = "java:module/concurrent/VirtualThreadExecutor", virtual = true ) public class ConcurrencyExample { - @Resource(lookup = "java:app/concurrent/VirtualThreadExecutor") + @Resource(lookup = "java:module/concurrent/VirtualThreadExecutor") private ManagedExecutorService executorService; @Resource @@ -405,14 +405,6 @@ public class ConcurrencyExample { }); } - public void scheduleTask() { - // Schedule a task to run periodically - scheduledExecutor.scheduleAtFixedRate( - () -> performMaintenanceTask(), - 0, 1, TimeUnit.HOURS - ); - } - private String processData() { return "Processed data"; } From a3a89fd1ba501df48da5efe61e887c9a7c88be8f Mon Sep 17 00:00:00 2001 From: emijiang Date: Thu, 7 May 2026 10:14:19 +0100 Subject: [PATCH 05/26] address review comments --- posts/2026-05-16-Jakarta EE 11 in Open Liberty.adoc | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/posts/2026-05-16-Jakarta EE 11 in Open Liberty.adoc b/posts/2026-05-16-Jakarta EE 11 in Open Liberty.adoc index 47e5632bd..9bab0001c 100644 --- a/posts/2026-05-16-Jakarta EE 11 in Open Liberty.adoc +++ b/posts/2026-05-16-Jakarta EE 11 in Open Liberty.adoc @@ -399,10 +399,7 @@ public class ConcurrencyExample { public CompletableFuture asyncOperation() { // Leverages Virtual Threads on Java 21+ - return executorService.supplyAsync(() -> { - // Long-running operation - return processData(); - }); + return executorService.supplyAsync(this::processData);; } private String processData() { From 26a5bc5ac276122adc9b7ca947a71160ace3138b Mon Sep 17 00:00:00 2001 From: emijiang Date: Thu, 7 May 2026 13:52:31 +0100 Subject: [PATCH 06/26] Add more code examples for Persistence 3.2 --- ...6-05-16-Jakarta EE 11 in Open Liberty.adoc | 233 +++++++++++++++++- 1 file changed, 229 insertions(+), 4 deletions(-) diff --git a/posts/2026-05-16-Jakarta EE 11 in Open Liberty.adoc b/posts/2026-05-16-Jakarta EE 11 in Open Liberty.adoc index 9bab0001c..0cad5f5f8 100644 --- a/posts/2026-05-16-Jakarta EE 11 in Open Liberty.adoc +++ b/posts/2026-05-16-Jakarta EE 11 in Open Liberty.adoc @@ -48,7 +48,7 @@ One of the most exciting additions to Jakarta EE 11 is *Jakarta Data 1.0*, a new === What is Jakarta Data? -Jakarta Data provides an API for easier data access. A Java developer can split the details of persistence from the data model with several features, such as the ability to compose custom query methods on a Repository interface. Jakarta Data's goal is to provide a familiar and consistent, Jakarta-based programming model for data access while still retaining the particular traits of the underlying data store. It is designed to be flexible and extensible, allowing developers to use it with various types of databases, including relational databases, NoSQL databases, and even in-memory data stores. +Jakarta Data provides an API for easier data access. A Java developer can split the persistence from the model with several features, such as the ability to compose custom query methods or a Repository interface. Jakarta Data's goal is to provide a familiar and consistent, Jakarta-based programming model for data access while still retaining the particular traits of the underlying data store. === Key Features of Jakarta Data 1.0 @@ -386,12 +386,12 @@ import java.util.concurrent.TimeUnit; @ApplicationScoped @ManagedExecutorDefinition( - name = "java:module/concurrent/VirtualThreadExecutor", + name = "java:app/concurrent/VirtualThreadExecutor", virtual = true ) public class ConcurrencyExample { - @Resource(lookup = "java:module/concurrent/VirtualThreadExecutor") + @Resource(lookup = "java:app/concurrent/VirtualThreadExecutor") private ManagedExecutorService executorService; @Resource @@ -399,7 +399,18 @@ public class ConcurrencyExample { public CompletableFuture asyncOperation() { // Leverages Virtual Threads on Java 21+ - return executorService.supplyAsync(this::processData);; + return executorService.supplyAsync(() -> { + // Long-running operation + return processData(); + }); + } + + public void scheduleTask() { + // Schedule a task to run periodically + scheduledExecutor.scheduleAtFixedRate( + () -> performMaintenanceTask(), + 0, 1, TimeUnit.HOURS + ); } private String processData() { @@ -1631,6 +1642,220 @@ public class CustomerRepository { } } } + +// NEW in 3.2: Criteria API Enhancements +@Stateless +public class CustomerCriteriaRepository { + + @PersistenceContext + private EntityManager em; + + // NEW in 3.2: Using CriteriaSelect for type-safe queries + public List findCustomersByCityUsingCriteria(String city) { + CriteriaBuilder cb = em.getCriteriaBuilder(); + CriteriaQuery cq = cb.createQuery(Customer.class); + Root customer = cq.from(Customer.class); + + // CriteriaSelect provides enhanced type safety + cq.select(customer) + .where(cb.equal(customer.get("address").get("city"), city)); + + return em.createQuery(cq).getResultList(); + } + + // NEW in 3.2: subquery(EntityType) for correlated subqueries + public List findCustomersWithMultipleOrders() { + CriteriaBuilder cb = em.getCriteriaBuilder(); + CriteriaQuery cq = cb.createQuery(Customer.class); + Root customer = cq.from(Customer.class); + + // Create a correlated subquery using the new subquery(EntityType) method + Subquery subquery = cq.subquery(Long.class); + Root order = subquery.correlate(customer).join("orders"); + subquery.select(cb.count(order)); + + cq.select(customer) + .where(cb.greaterThan(subquery, 1L)); + + return em.createQuery(cq).getResultList(); + } + + // NEW in 3.2: Joins on EntityType + public List findCustomersWithRecentOrders(Instant since) { + CriteriaBuilder cb = em.getCriteriaBuilder(); + CriteriaQuery cq = cb.createQuery(Customer.class); + Root customer = cq.from(Customer.class); + + // Join directly on EntityType + Join orders = customer.join("orders"); + + cq.select(customer) + .where(cb.greaterThanOrEqualTo(orders.get("orderDate"), since)) + .distinct(true); + + return em.createQuery(cq).getResultList(); + } +} + +// NEW in 3.2: Advanced Query Operators +@Stateless +public class AdvancedQueryRepository { + + @PersistenceContext + private EntityManager em; + + // NEW in 3.2: INTERSECT operator + public List findCustomersInBothActiveAndVIP() { + return em.createQuery( + "SELECT c FROM Customer c WHERE c.status = 'ACTIVE' " + + "INTERSECT " + + "SELECT c FROM Customer c WHERE c.vipLevel > 5", + Customer.class + ).getResultList(); + } + + // NEW in 3.2: EXCEPT operator (set difference) + public List findActiveCustomersExceptVIP() { + return em.createQuery( + "SELECT c FROM Customer c WHERE c.status = 'ACTIVE' " + + "EXCEPT " + + "SELECT c FROM Customer c WHERE c.vipLevel > 5", + Customer.class + ).getResultList(); + } + + // NEW in 3.2: CAST operator for type conversion + public List getCustomerIdsAsStrings() { + return em.createQuery( + "SELECT CAST(c.id AS STRING) FROM Customer c", + String.class + ).getResultList(); + } + + // NEW in 3.2: LEFT and RIGHT string functions + public List getEmailPrefixes() { + return em.createQuery( + "SELECT LEFT(c.email, 5) FROM Customer c", + String.class + ).getResultList(); + } + + public List getEmailDomains() { + return em.createQuery( + "SELECT RIGHT(c.email, 10) FROM Customer c", + String.class + ).getResultList(); + } + + // NEW in 3.2: REPLACE function for string manipulation + public List normalizePhoneNumbers() { + return em.createQuery( + "SELECT REPLACE(c.phone, '-', '') FROM Customer c", + String.class + ).getResultList(); + } + + // NEW in 3.2: id() function to access entity identifier + public List getCustomerIds() { + return em.createQuery( + "SELECT id(c) FROM Customer c WHERE c.status = 'ACTIVE'", + Long.class + ).getResultList(); + } + + // NEW in 3.2: version() function to access entity version + public List getCustomerVersionInfo() { + return em.createQuery( + "SELECT c.name, version(c) FROM Customer c", + Object[].class + ).getResultList(); + } +} + +// NEW in 3.2: NamedNativeQuery with entities(), classes(), and columns() +@Entity +@Table(name = "products") +@NamedNativeQuery( + name = "Product.findWithDetails", + query = "SELECT p.id, p.name, p.price, c.name as category_name " + + "FROM products p JOIN categories c ON p.category_id = c.id", + resultSetMapping = "ProductDetailsMapping" +) +@SqlResultSetMapping( + name = "ProductDetailsMapping", + entities = @EntityResult( + entityClass = Product.class, + fields = { + @FieldResult(name = "id", column = "id"), + @FieldResult(name = "name", column = "name"), + @FieldResult(name = "price", column = "price") + }, + // NEW in 3.2: lockMode() on EntityResult + lockMode = LockModeType.OPTIMISTIC + ), + columns = @ColumnResult(name = "category_name", type = String.class) +) +public class Product { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + private String name; + private Double price; + + @Version + private Long version; + + @ManyToOne + @JoinColumn(name = "category_id") + private Category category; + + // Constructors, getters, setters +} + +@Entity +@Table(name = "categories") +public class Category { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + private String name; + + @OneToMany(mappedBy = "category") + private List products; + + // Constructors, getters, setters +} + +@Stateless +public class ProductRepository { + + @PersistenceContext + private EntityManager em; + + // NEW in 3.2: Using NamedNativeQuery with enhanced result mapping + public List findProductsWithDetails() { + return em.createNamedQuery("Product.findWithDetails") + .getResultList(); + } + + // NEW in 3.2: Combining multiple new features + public List findProductsWithComplexCriteria(Double minPrice, String namePattern) { + return em.createQuery( + "SELECT p FROM Product p " + + "WHERE p.price >= :minPrice " + + "AND REPLACE(LOWER(p.name), ' ', '') LIKE :pattern " + + "ORDER BY p.price NULLS LAST, p.name", + Product.class + ) + .setParameter("minPrice", minPrice) + .setParameter("pattern", namePattern) + .getSingleResultOrNull(); // Returns null if no results + } +} ---- [#websocket-2.2] From 9b61abc7c7a1e4ef47a1050b56c024edb302e2a8 Mon Sep 17 00:00:00 2001 From: emijiang Date: Thu, 7 May 2026 14:00:12 +0100 Subject: [PATCH 07/26] undo some changes --- ...6-05-16-Jakarta EE 11 in Open Liberty.adoc | 233 +----------------- 1 file changed, 4 insertions(+), 229 deletions(-) diff --git a/posts/2026-05-16-Jakarta EE 11 in Open Liberty.adoc b/posts/2026-05-16-Jakarta EE 11 in Open Liberty.adoc index 0cad5f5f8..9bab0001c 100644 --- a/posts/2026-05-16-Jakarta EE 11 in Open Liberty.adoc +++ b/posts/2026-05-16-Jakarta EE 11 in Open Liberty.adoc @@ -48,7 +48,7 @@ One of the most exciting additions to Jakarta EE 11 is *Jakarta Data 1.0*, a new === What is Jakarta Data? -Jakarta Data provides an API for easier data access. A Java developer can split the persistence from the model with several features, such as the ability to compose custom query methods or a Repository interface. Jakarta Data's goal is to provide a familiar and consistent, Jakarta-based programming model for data access while still retaining the particular traits of the underlying data store. +Jakarta Data provides an API for easier data access. A Java developer can split the details of persistence from the data model with several features, such as the ability to compose custom query methods on a Repository interface. Jakarta Data's goal is to provide a familiar and consistent, Jakarta-based programming model for data access while still retaining the particular traits of the underlying data store. It is designed to be flexible and extensible, allowing developers to use it with various types of databases, including relational databases, NoSQL databases, and even in-memory data stores. === Key Features of Jakarta Data 1.0 @@ -386,12 +386,12 @@ import java.util.concurrent.TimeUnit; @ApplicationScoped @ManagedExecutorDefinition( - name = "java:app/concurrent/VirtualThreadExecutor", + name = "java:module/concurrent/VirtualThreadExecutor", virtual = true ) public class ConcurrencyExample { - @Resource(lookup = "java:app/concurrent/VirtualThreadExecutor") + @Resource(lookup = "java:module/concurrent/VirtualThreadExecutor") private ManagedExecutorService executorService; @Resource @@ -399,18 +399,7 @@ public class ConcurrencyExample { public CompletableFuture asyncOperation() { // Leverages Virtual Threads on Java 21+ - return executorService.supplyAsync(() -> { - // Long-running operation - return processData(); - }); - } - - public void scheduleTask() { - // Schedule a task to run periodically - scheduledExecutor.scheduleAtFixedRate( - () -> performMaintenanceTask(), - 0, 1, TimeUnit.HOURS - ); + return executorService.supplyAsync(this::processData);; } private String processData() { @@ -1642,220 +1631,6 @@ public class CustomerRepository { } } } - -// NEW in 3.2: Criteria API Enhancements -@Stateless -public class CustomerCriteriaRepository { - - @PersistenceContext - private EntityManager em; - - // NEW in 3.2: Using CriteriaSelect for type-safe queries - public List findCustomersByCityUsingCriteria(String city) { - CriteriaBuilder cb = em.getCriteriaBuilder(); - CriteriaQuery cq = cb.createQuery(Customer.class); - Root customer = cq.from(Customer.class); - - // CriteriaSelect provides enhanced type safety - cq.select(customer) - .where(cb.equal(customer.get("address").get("city"), city)); - - return em.createQuery(cq).getResultList(); - } - - // NEW in 3.2: subquery(EntityType) for correlated subqueries - public List findCustomersWithMultipleOrders() { - CriteriaBuilder cb = em.getCriteriaBuilder(); - CriteriaQuery cq = cb.createQuery(Customer.class); - Root customer = cq.from(Customer.class); - - // Create a correlated subquery using the new subquery(EntityType) method - Subquery subquery = cq.subquery(Long.class); - Root order = subquery.correlate(customer).join("orders"); - subquery.select(cb.count(order)); - - cq.select(customer) - .where(cb.greaterThan(subquery, 1L)); - - return em.createQuery(cq).getResultList(); - } - - // NEW in 3.2: Joins on EntityType - public List findCustomersWithRecentOrders(Instant since) { - CriteriaBuilder cb = em.getCriteriaBuilder(); - CriteriaQuery cq = cb.createQuery(Customer.class); - Root customer = cq.from(Customer.class); - - // Join directly on EntityType - Join orders = customer.join("orders"); - - cq.select(customer) - .where(cb.greaterThanOrEqualTo(orders.get("orderDate"), since)) - .distinct(true); - - return em.createQuery(cq).getResultList(); - } -} - -// NEW in 3.2: Advanced Query Operators -@Stateless -public class AdvancedQueryRepository { - - @PersistenceContext - private EntityManager em; - - // NEW in 3.2: INTERSECT operator - public List findCustomersInBothActiveAndVIP() { - return em.createQuery( - "SELECT c FROM Customer c WHERE c.status = 'ACTIVE' " + - "INTERSECT " + - "SELECT c FROM Customer c WHERE c.vipLevel > 5", - Customer.class - ).getResultList(); - } - - // NEW in 3.2: EXCEPT operator (set difference) - public List findActiveCustomersExceptVIP() { - return em.createQuery( - "SELECT c FROM Customer c WHERE c.status = 'ACTIVE' " + - "EXCEPT " + - "SELECT c FROM Customer c WHERE c.vipLevel > 5", - Customer.class - ).getResultList(); - } - - // NEW in 3.2: CAST operator for type conversion - public List getCustomerIdsAsStrings() { - return em.createQuery( - "SELECT CAST(c.id AS STRING) FROM Customer c", - String.class - ).getResultList(); - } - - // NEW in 3.2: LEFT and RIGHT string functions - public List getEmailPrefixes() { - return em.createQuery( - "SELECT LEFT(c.email, 5) FROM Customer c", - String.class - ).getResultList(); - } - - public List getEmailDomains() { - return em.createQuery( - "SELECT RIGHT(c.email, 10) FROM Customer c", - String.class - ).getResultList(); - } - - // NEW in 3.2: REPLACE function for string manipulation - public List normalizePhoneNumbers() { - return em.createQuery( - "SELECT REPLACE(c.phone, '-', '') FROM Customer c", - String.class - ).getResultList(); - } - - // NEW in 3.2: id() function to access entity identifier - public List getCustomerIds() { - return em.createQuery( - "SELECT id(c) FROM Customer c WHERE c.status = 'ACTIVE'", - Long.class - ).getResultList(); - } - - // NEW in 3.2: version() function to access entity version - public List getCustomerVersionInfo() { - return em.createQuery( - "SELECT c.name, version(c) FROM Customer c", - Object[].class - ).getResultList(); - } -} - -// NEW in 3.2: NamedNativeQuery with entities(), classes(), and columns() -@Entity -@Table(name = "products") -@NamedNativeQuery( - name = "Product.findWithDetails", - query = "SELECT p.id, p.name, p.price, c.name as category_name " + - "FROM products p JOIN categories c ON p.category_id = c.id", - resultSetMapping = "ProductDetailsMapping" -) -@SqlResultSetMapping( - name = "ProductDetailsMapping", - entities = @EntityResult( - entityClass = Product.class, - fields = { - @FieldResult(name = "id", column = "id"), - @FieldResult(name = "name", column = "name"), - @FieldResult(name = "price", column = "price") - }, - // NEW in 3.2: lockMode() on EntityResult - lockMode = LockModeType.OPTIMISTIC - ), - columns = @ColumnResult(name = "category_name", type = String.class) -) -public class Product { - - @Id - @GeneratedValue(strategy = GenerationType.IDENTITY) - private Long id; - - private String name; - private Double price; - - @Version - private Long version; - - @ManyToOne - @JoinColumn(name = "category_id") - private Category category; - - // Constructors, getters, setters -} - -@Entity -@Table(name = "categories") -public class Category { - - @Id - @GeneratedValue(strategy = GenerationType.IDENTITY) - private Long id; - - private String name; - - @OneToMany(mappedBy = "category") - private List products; - - // Constructors, getters, setters -} - -@Stateless -public class ProductRepository { - - @PersistenceContext - private EntityManager em; - - // NEW in 3.2: Using NamedNativeQuery with enhanced result mapping - public List findProductsWithDetails() { - return em.createNamedQuery("Product.findWithDetails") - .getResultList(); - } - - // NEW in 3.2: Combining multiple new features - public List findProductsWithComplexCriteria(Double minPrice, String namePattern) { - return em.createQuery( - "SELECT p FROM Product p " + - "WHERE p.price >= :minPrice " + - "AND REPLACE(LOWER(p.name), ' ', '') LIKE :pattern " + - "ORDER BY p.price NULLS LAST, p.name", - Product.class - ) - .setParameter("minPrice", minPrice) - .setParameter("pattern", namePattern) - .getSingleResultOrNull(); // Returns null if no results - } -} ---- [#websocket-2.2] From 286460c04878b97f84bdbf70f39515cd6da060d5 Mon Sep 17 00:00:00 2001 From: emijiang Date: Thu, 7 May 2026 17:17:59 +0100 Subject: [PATCH 08/26] Address the remaining review comments --- ...6-05-16-Jakarta EE 11 in Open Liberty.adoc | 603 ++++++++++++++++-- 1 file changed, 548 insertions(+), 55 deletions(-) diff --git a/posts/2026-05-16-Jakarta EE 11 in Open Liberty.adoc b/posts/2026-05-16-Jakarta EE 11 in Open Liberty.adoc index 9bab0001c..4dd42b15a 100644 --- a/posts/2026-05-16-Jakarta EE 11 in Open Liberty.adoc +++ b/posts/2026-05-16-Jakarta EE 11 in Open Liberty.adoc @@ -37,6 +37,7 @@ Jakarta EE 11 represents a major step forward for cloud-native enterprise Java a * <> * <> * <> +* <> * <> * <> @@ -405,8 +406,10 @@ public class ConcurrencyExample { private String processData() { return "Processed data"; } - - private void performMaintenanceTask() { + @Asynchronous( + executor = "java:module/concurrent/VirtualExecutor", + runAt = @Schedule(cron = "0 3 * * *")) // daily at 3 AM + private void performMaintenanceTask(@Observes Startup event) { // Maintenance logic } } @@ -650,45 +653,38 @@ Support for `java.lang.Optional` is available but disabled by default. To enable [source,java] ---- + import jakarta.el.*; import java.util.Optional; public class OptionalELResolverExample { - + public static void main(String[] args) { // Create EL context ExpressionFactory factory = ExpressionFactory.newInstance(); StandardELContext context = new StandardELContext(factory); - - // NEW in EL 6.0: Add OptionalELResolver to support Optional - // Note: This is disabled by default and must be explicitly added - CompositeELResolver resolver = new CompositeELResolver(); - resolver.add(new OptionalELResolver()); - resolver.add(new BeanELResolver()); - resolver.add(new ArrayELResolver()); - resolver.add(new ListELResolver()); - resolver.add(new MapELResolver()); - context.addELResolver(resolver); - + + context.addELResolver(new OptionalELResolver()); + // Create Optional values Optional presentValue = Optional.of("Hello, EL 6.0!"); Optional emptyValue = Optional.empty(); - + context.getELResolver().setValue(context, null, "message", presentValue); - context.getELResolver().setValue(context, null, "empty", emptyValue); - + context.getELResolver().setValue(context, null, "emptyValue", emptyValue); + // Access Optional values - automatically unwrapped if present ValueExpression messageExpr = factory.createValueExpression( context, "${message}", String.class); String message = (String) messageExpr.getValue(context); System.out.println("Message: " + message); // Output: Hello, EL 6.0! - + // Empty Optional returns null ValueExpression emptyExpr = factory.createValueExpression( - context, "${empty}", String.class); + context, "${emptyValue}", String.class); String emptyResult = (String) emptyExpr.getValue(context); - System.out.println("Empty: " + emptyResult); // Output: null - + System.out.println("emptyValue: " + emptyResult); // Output: null + // Use with conditional expressions ValueExpression hasMsgExpr = factory.createValueExpression( context, "${message != null}", Boolean.class); @@ -1631,6 +1627,533 @@ public class CustomerRepository { } } } +// NEW in 3.2: Criteria API Enhancements +@Stateless +public class CustomerCriteriaRepository { + + @PersistenceContext + private EntityManager em; + + // NEW in 3.2: Using CriteriaSelect for type-safe queries + public List findCustomersByCityUsingCriteria(String city) { + CriteriaBuilder cb = em.getCriteriaBuilder(); + CriteriaQuery cq = cb.createQuery(Customer.class); + Root customer = cq.from(Customer.class); + + // CriteriaSelect provides enhanced type safety + cq.select(customer) + .where(cb.equal(customer.get("address").get("city"), city)); + + return em.createQuery(cq).getResultList(); + } + + // NEW in 3.2: subquery(EntityType) for correlated subqueries + public List findCustomersWithMultipleOrders() { + CriteriaBuilder cb = em.getCriteriaBuilder(); + CriteriaQuery cq = cb.createQuery(Customer.class); + Root customer = cq.from(Customer.class); + + // Create a correlated subquery using the new subquery(EntityType) method + Subquery subquery = cq.subquery(Long.class); + Root order = subquery.correlate(customer).join("orders"); + subquery.select(cb.count(order)); + + cq.select(customer) + .where(cb.greaterThan(subquery, 1L)); + + return em.createQuery(cq).getResultList(); + } + + // NEW in 3.2: Joins on EntityType + public List findCustomersWithRecentOrders(Instant since) { + CriteriaBuilder cb = em.getCriteriaBuilder(); + CriteriaQuery cq = cb.createQuery(Customer.class); + Root customer = cq.from(Customer.class); + + // Join directly on EntityType + Join orders = customer.join("orders"); + + cq.select(customer) + .where(cb.greaterThanOrEqualTo(orders.get("orderDate"), since)) + .distinct(true); + + return em.createQuery(cq).getResultList(); + } +} + +// NEW in 3.2: Advanced Query Operators +@Stateless +public class AdvancedQueryRepository { + + @PersistenceContext + private EntityManager em; + + // NEW in 3.2: INTERSECT operator + public List findCustomersInBothActiveAndVIP() { + return em.createQuery( + "SELECT c FROM Customer c WHERE c.status = 'ACTIVE' " + + "INTERSECT " + + "SELECT c FROM Customer c WHERE c.vipLevel > 5", + Customer.class + ).getResultList(); + } + + // NEW in 3.2: EXCEPT operator (set difference) + public List findActiveCustomersExceptVIP() { + return em.createQuery( + "SELECT c FROM Customer c WHERE c.status = 'ACTIVE' " + + "EXCEPT " + + "SELECT c FROM Customer c WHERE c.vipLevel > 5", + Customer.class + ).getResultList(); + } + + // NEW in 3.2: CAST operator for type conversion + public List getCustomerIdsAsStrings() { + return em.createQuery( + "SELECT CAST(c.id AS STRING) FROM Customer c", + String.class + ).getResultList(); + } + + // NEW in 3.2: LEFT and RIGHT string functions + public List getEmailPrefixes() { + return em.createQuery( + "SELECT LEFT(c.email, 5) FROM Customer c", + String.class + ).getResultList(); + } + + public List getEmailDomains() { + return em.createQuery( + "SELECT RIGHT(c.email, 10) FROM Customer c", + String.class + ).getResultList(); + } + + // NEW in 3.2: REPLACE function for string manipulation + public List normalizePhoneNumbers() { + return em.createQuery( + "SELECT REPLACE(c.phone, '-', '') FROM Customer c", + String.class + ).getResultList(); + } + + // NEW in 3.2: id() function to access entity identifier + public List getCustomerIds() { + return em.createQuery( + "SELECT id(c) FROM Customer c WHERE c.status = 'ACTIVE'", + Long.class + ).getResultList(); + } + + // NEW in 3.2: version() function to access entity version + public List getCustomerVersionInfo() { + return em.createQuery( + "SELECT c.name, version(c) FROM Customer c", + Object[].class + ).getResultList(); + } +} + +// NEW in 3.2: NamedNativeQuery with entities(), classes(), and columns() +@Entity +@Table(name = "products") +@NamedNativeQuery( + name = "Product.findWithDetails", + query = "SELECT p.id, p.name, p.price, c.name as category_name " + + "FROM products p JOIN categories c ON p.category_id = c.id", + resultSetMapping = "ProductDetailsMapping" +) +@SqlResultSetMapping( + name = "ProductDetailsMapping", + entities = @EntityResult( + entityClass = Product.class, + fields = { + @FieldResult(name = "id", column = "id"), + @FieldResult(name = "name", column = "name"), + @FieldResult(name = "price", column = "price") + }, + // NEW in 3.2: lockMode() on EntityResult + lockMode = LockModeType.OPTIMISTIC + ), + columns = @ColumnResult(name = "category_name", type = String.class) +) +public class Product { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + private String name; + private Double price; + + @Version + private Long version; + + @ManyToOne + @JoinColumn(name = "category_id") + private Category category; + + // Constructors, getters, setters +} + +@Entity +@Table(name = "categories") +public class Category { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + private String name; + + @OneToMany(mappedBy = "category") + private List products; + + // Constructors, getters, setters +} + +@Stateless +public class ProductRepository { + + @PersistenceContext + private EntityManager em; + + // NEW in 3.2: Using NamedNativeQuery with enhanced result mapping + public List findProductsWithDetails() { + return em.createNamedQuery("Product.findWithDetails") + .getResultList(); + } + + // NEW in 3.2: Combining multiple new features + public List findProductsWithComplexCriteria(Double minPrice, String namePattern) { + return em.createQuery( + "SELECT p FROM Product p " + + "WHERE p.price >= :minPrice " + + "AND REPLACE(LOWER(p.name), ' ', '') LIKE :pattern " + + "ORDER BY p.price NULLS LAST, p.name", + Product.class + ) + .setParameter("minPrice", minPrice) + .setParameter("pattern", namePattern) + .getSingleResultOrNull(); // Returns null if no results + } +} +---- + +[#pages-4.0] +=== Pages 4.0 + +Jakarta Pages 4.0 (formerly JavaServer Pages or JSP) defines a template engine for web applications. This release modernizes the specification by removing deprecated features and aligning with Jakarta EE 11 requirements. + +*New features and changes in Pages 4.0:* + +- *Removal of deprecated features*: Removed deprecated JSP 1.x style syntax and features +- *Updated namespace*: All package names updated from `javax.*` to `jakarta.*` +- *Expression Language 6.0 integration*: Full integration with the latest Expression Language specification +- *Improved error handling*: Enhanced error reporting and debugging capabilities +- *Java 17+ requirement*: Requires Java SE 17 or later as the minimum version + +==== Basic JSP Page with EL 6.0 Features + +Jakarta Pages 4.0 works seamlessly with Expression Language 6.0, allowing you to use modern Java features like Records and Optional: + +[source,jsp] +---- +<%@ page contentType="text/html;charset=UTF-8" language="java" %> +<%@ taglib prefix="c" uri="jakarta.tags.core" %> + + + + Product Catalog - Jakarta Pages 4.0 + + +

Product Catalog

+ + + +
+

${product.name}

+

Price: $${product.price}

+ + + +

Location: ${product.address.city}, ${product.address.state}

+
+ + +

Discount: ${product.discount.orElse(0)}%

+ + +

Available in ${product.sizes.length} sizes

+
+
+ + +---- + +==== Custom Tag with Jakarta Pages 4.0 + +Creating custom tags in Jakarta Pages 4.0 with updated namespace: + +[source,java] +---- +package com.example.tags; + +import jakarta.servlet.jsp.JspException; +import jakarta.servlet.jsp.tagext.SimpleTagSupport; +import java.io.IOException; +import java.time.LocalDateTime; +import java.time.format.DateTimeFormatter; + +// NEW in Pages 4.0: Using jakarta.servlet.jsp namespace +public class FormatDateTag extends SimpleTagSupport { + + private LocalDateTime date; + private String pattern = "yyyy-MM-dd HH:mm:ss"; + + public void setDate(LocalDateTime date) { + this.date = date; + } + + public void setPattern(String pattern) { + this.pattern = pattern; + } + + @Override + public void doTag() throws JspException, IOException { + if (date != null) { + DateTimeFormatter formatter = DateTimeFormatter.ofPattern(pattern); + getJspContext().getOut().write(date.format(formatter)); + } + } +} +---- + +Tag Library Descriptor (TLD) file: + +[source,xml] +---- + + + + 1.0 + custom + http://example.com/tags + + + formatDate + com.example.tags.FormatDateTag + empty + + date + true + true + java.time.LocalDateTime + + + pattern + false + true + + + +---- + +==== Using Custom Tags in JSP + +[source,jsp] +---- +<%@ page contentType="text/html;charset=UTF-8" language="java" %> +<%@ taglib prefix="c" uri="jakarta.tags.core" %> +<%@ taglib prefix="custom" uri="http://example.com/tags" %> + + + + Order Details + + +

Order #${order.id}

+ + +

Order Date:

+

Delivery Date:

+ +

Items

+ + + + + + + + + + + + + + + + + + + +
ProductQuantityPriceTotal
${item.product.name}${item.quantity}$${item.product.price}$${item.quantity * item.product.price}
+ + +

Total: $${order.total} (${order.items.length} ${order.items.length == 1 ? 'item' : 'items'})

+ + +---- + +==== JSP with CDI Integration + +Jakarta Pages 4.0 integrates seamlessly with CDI 4.1: + +[source,java] +---- +package com.example.beans; + +import jakarta.enterprise.context.RequestScoped; +import jakarta.inject.Named; +import jakarta.inject.Inject; +import java.util.List; + +@Named +@RequestScoped +public class ProductBean { + + @Inject + private ProductService productService; + + private String searchTerm; + private List searchResults; + + public void search() { + if (searchTerm != null && !searchTerm.isEmpty()) { + searchResults = productService.searchProducts(searchTerm); + } + } + + // Getters and setters + public String getSearchTerm() { + return searchTerm; + } + + public void setSearchTerm(String searchTerm) { + this.searchTerm = searchTerm; + } + + public List getSearchResults() { + return searchResults; + } +} +---- + +JSP page using the CDI bean: + +[source,jsp] +---- +<%@ page contentType="text/html;charset=UTF-8" language="java" %> +<%@ taglib prefix="c" uri="jakarta.tags.core" %> + + + + Product Search + + +

Search Products

+ +
+ + +
+ + +

Search Results (${productBean.searchResults.length} found)

+
    + +
  • + ${product.name} - $${product.price} + + Only ${product.stockQuantity} left! + +
  • +
    +
+
+ + +

No products found matching "${productBean.searchTerm}"

+
+ + +---- + +==== Error Handling in Jakarta Pages 4.0 + +Improved error handling with custom error pages: + +[source,jsp] +---- +<%@ page contentType="text/html;charset=UTF-8" language="java" isErrorPage="true" %> +<%@ taglib prefix="c" uri="jakarta.tags.core" %> + + + + Error - ${pageContext.errorData.statusCode} + + +

An Error Occurred

+ +
+

Status Code: ${pageContext.errorData.statusCode}

+

Request URI: ${pageContext.errorData.requestURI}

+ + +

Exception: ${pageContext.exception.class.name}

+

Message: ${pageContext.exception.message}

+
+
+ +

Return to Home

+ + +---- + +Configure error page in `web.xml`: + +[source,xml] +---- + + + + + 404 + /error.jsp + + + + 500 + /error.jsp + + + + java.lang.Exception + /error.jsp + + ---- [#websocket-2.2] @@ -2084,46 +2607,16 @@ Key areas to review: - *Validation 3.1*: Leverage Java Records for validated data structures - *Concurrency 3.1*: Test async operations, especially if using Java 21 Virtual Threads -== Performance Benefits with Java 21 - -Jakarta EE 11's support for Java 21 brings significant performance improvements: - -[source,java] ----- -// Virtual Threads example with Jakarta Concurrency -@ApplicationScoped -public class VirtualThreadExample { - - @Resource - private ManagedExecutorService executor; - - public void processLargeDataset(List dataset) { - // Each task runs on a virtual thread - List> futures = dataset.stream() - .map(data -> CompletableFuture.supplyAsync( - () -> processData(data), - executor - )) - .toList(); - - // Wait for all to complete - CompletableFuture.allOf(futures.toArray(new CompletableFuture[0])) - .join(); - } - - private Result processData(Data data) { - // Processing logic - return new Result(); - } -} ----- - == Conclusion Jakarta EE 11 in Open Liberty 26.0.0.5 represents a significant advancement in enterprise Java development. The introduction of Jakarta Data 1.0 alone is a game-changer, dramatically reducing boilerplate code and improving developer productivity. Combined with updates across all major specifications, enhanced performance through Java 21 support, and a comprehensive set of modern APIs, Jakarta EE 11 provides a solid foundation for building cloud-native enterprise applications. The combination of Open Liberty's lightweight, fast runtime and Jakarta EE 11's powerful features makes this an excellent choice for organizations looking to modernize their enterprise Java applications while maintaining compatibility with industry standards. +What's more? Jakarta EE 11 also works with MicroProfile 7.0 and 7.1 in Open Liberty, allowing you to take advantage of the latest microservices features alongside the core Jakarta EE platform. Whether you're building new applications or migrating existing ones, Open Liberty and Jakarta EE 11 provide the tools and capabilities you need to succeed in today's fast-paced development environment. + +If you are using Spring Boot, Open Liberty 26.0.0.5 and later release also enables you to use Spring Boot 4.0 with Jakarta EE 11 features, providing even more flexibility in how you build your applications. + == Learn More - link:https://jakarta.ee/specifications/platform/11/[Jakarta EE 11 Specifications] From cde557de174f1f9a06903cf124d084ba987e8501 Mon Sep 17 00:00:00 2001 From: emijiang Date: Mon, 11 May 2026 11:05:04 +0100 Subject: [PATCH 09/26] Address review comments --- ...6-05-16-Jakarta EE 11 in Open Liberty.adoc | 389 ++++++++++++++++-- 1 file changed, 358 insertions(+), 31 deletions(-) diff --git a/posts/2026-05-16-Jakarta EE 11 in Open Liberty.adoc b/posts/2026-05-16-Jakarta EE 11 in Open Liberty.adoc index 4dd42b15a..8733e21ae 100644 --- a/posts/2026-05-16-Jakarta EE 11 in Open Liberty.adoc +++ b/posts/2026-05-16-Jakarta EE 11 in Open Liberty.adoc @@ -6,8 +6,8 @@ categories: blog author_picture: https://avatars3.githubusercontent.com/emily-jiang author_github: https://github.com/Emily-Jiang seo-title: Jakarta EE 11 from Newbie to Pro with Open Liberty -seo-description: Discover Jakarta EE 11's revolutionary features in Open Liberty 26.0.0.5 or later releases, including the game-changing Jakarta Data 1.0 specification that eliminates boilerplate code. Explore comprehensive code examples for updated specifications like CDI 4.1, Persistence 3.2, Security 4.0, and RESTful Web Services 4.0. Learn how Java 21 Records further simplify your applications. -blog_description: Discover Jakarta EE 11's revolutionary features in Open Liberty 26.0.0.5 or later releases, including the game-changing Jakarta Data 1.0 specification that eliminates boilerplate code. Explore comprehensive code examples for updated specifications like CDI 4.1, Persistence 3.2, Security 4.0, and RESTful Web Services 4.0. Learn how Java 21 Records further simplify your applications. +seo-description: Discover Jakarta EE 11's revolutionary features in Open Liberty 26.0.0.5 or later releases, including the game-changing Jakarta Data 1.0 specification that eliminates boilerplate code. Explore comprehensive code examples for updated specifications like CDI 4.1, Persistence 3.2, Security 4.0, and RESTful Web Services 4.0. Learn how Java 17 Records further simplify your applications. +blog_description: Discover Jakarta EE 11's revolutionary features in Open Liberty 26.0.0.5 or later releases, including the game-changing Jakarta Data 1.0 specification that eliminates boilerplate code. Explore comprehensive code examples for updated specifications like CDI 4.1, Persistence 3.2, Security 4.0, and RESTful Web Services 4.0. Learn how Java 17 Records further simplify your applications. open-graph-image: https://openliberty.io/img/twitter_card.jpg open-graph-image-alt: Open Liberty Logo --- @@ -26,20 +26,22 @@ link:https://jakarta.ee/release/11/[Jakarta EE 11] marks a significant milestone Jakarta EE 11 represents a major step forward for cloud-native enterprise Java applications. This release includes modernizing and restructuring the Test Compatibility Kits (TCKs), the new Jakarta Data specification, major updates to existing specifications, and support for the latest Java LTS release, which enables developers to leverage enhancements in Java 21, including Virtual Threads, Records. Full lists are: -* <> +* <> * <> * <> * <> +* <> +* <> * <> -* <> +* <> * <> -* <> -* <> -* <> -* <> -* <> -* <> -* <> +* <> +* <> +* <> +* <> +* <> +* <> +* <> [#data-1.0] @@ -226,24 +228,32 @@ import jakarta.security.auth.message.MessagePolicy; import jakarta.security.auth.message.module.ServerAuthModule; import jakarta.security.auth.message.callback.CallerPrincipalCallback; import jakarta.security.auth.message.callback.GroupPrincipalCallback; -import javax.security.auth.Subject; -import javax.security.auth.callback.CallbackHandler; -import javax.security.auth.callback.Callback; +import jakarta.security.auth.message.callback.Callback; +import jakarta.security.auth.message.callback.CallbackHandler; +import jakarta.security.auth.message.callback.UnsupportedCallbackException; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import java.io.IOException; +import java.security.Principal; import java.util.Map; +import java.util.Set; public class CustomServerAuthModule implements ServerAuthModule { private CallbackHandler handler; @Override + @SuppressWarnings("rawtypes") public void initialize(MessagePolicy requestPolicy, MessagePolicy responsePolicy, CallbackHandler handler, Map options) throws AuthException { this.handler = handler; } @Override - public AuthStatus validateRequest(MessageInfo messageInfo, Subject clientSubject, - Subject serviceSubject) throws AuthException { + public AuthStatus validateRequest(MessageInfo messageInfo, + jakarta.security.auth.message.callback.Subject clientSubject, + jakarta.security.auth.message.callback.Subject serviceSubject) + throws AuthException { // Extract credentials from request String username = extractUsername(messageInfo); String password = extractPassword(messageInfo); @@ -259,7 +269,7 @@ public class CustomServerAuthModule implements ServerAuthModule { try { handler.handle(new Callback[]{principalCallback, groupCallback}); - } catch (Exception e) { + } catch (IOException | UnsupportedCallbackException e) { throw new AuthException(e.getMessage()); } @@ -270,22 +280,27 @@ public class CustomServerAuthModule implements ServerAuthModule { } @Override - public AuthStatus secureResponse(MessageInfo messageInfo, Subject serviceSubject) + public AuthStatus secureResponse(MessageInfo messageInfo, + jakarta.security.auth.message.callback.Subject serviceSubject) throws AuthException { return AuthStatus.SEND_SUCCESS; } @Override - public void cleanSubject(MessageInfo messageInfo, Subject subject) throws AuthException { + public void cleanSubject(MessageInfo messageInfo, + jakarta.security.auth.message.callback.Subject subject) + throws AuthException { if (subject != null) { - subject.getPrincipals().clear(); + Set principals = subject.getPrincipals(); + if (principals != null) { + principals.clear(); + } } } @Override - public Class[] getSupportedMessageTypes() { - return new Class[]{jakarta.servlet.http.HttpServletRequest.class, - jakarta.servlet.http.HttpServletResponse.class}; + public Class[] getSupportedMessageTypes() { + return new Class[]{HttpServletRequest.class, HttpServletResponse.class}; } private String extractUsername(MessageInfo messageInfo) { @@ -400,7 +415,7 @@ public class ConcurrencyExample { public CompletableFuture asyncOperation() { // Leverages Virtual Threads on Java 21+ - return executorService.supplyAsync(this::processData);; + return executorService.supplyAsync(this::processData); } private String processData() { @@ -415,6 +430,50 @@ public class ConcurrencyExample { } ---- +[#annotations-3.0] +=== Annotations 3.0 + +Jakarta Annotations 3.0 defines a collection of annotations representing common semantic concepts that enable a declarative style of programming in the Jakarta EE platform. + +*What's New in Annotations 3.0:* + +- **N/A** - No new features, enhancements, or additions + +*Changes in Annotations 3.0:* + +- **Removal of @ManagedBean**: The deprecated `@ManagedBean` annotation has been fully removed from the specification. Developers must migrate to CDI managed beans using `@Named` and appropriate scope annotations. + +==== Migrating from @ManagedBean + +If your application uses `@ManagedBean`, you must migrate to CDI managed beans. The `@ManagedBean` annotation was deprecated in Java EE 6 and has been removed in Jakarta EE 11. + +**Migration Options:** + +1. **Use CDI `@Named` for JSF/EL access** - If the bean needs to be accessible from JSF pages or Expression Language +2. **Use CDI scope annotations without `@Named`** - If the bean is only used for dependency injection +3. **Use EJB annotations** - If the bean requires transaction management or other EJB services + +[#interceptors-2.2] +=== Interceptors 2.2 + +Jakarta Interceptors 2.2 defines a means of interposing on business method invocations and specific events in the lifecycle of beans. + +*New features, enhancements or additions:* + +- **Updated dependencies for Jakarta EE 11** + - Jakarta Annotations to 3.0.0 +- **Add standard accessor to interceptor bindings** - Provides a standard way to access interceptor bindings from `InvocationContext` +- **Provide access to interceptor bindings from InvocationContext** - New method `getInterceptorBindings()` added to `InvocationContext` interface +- **Improve InvocationContext.getInterceptorBindings() language** + - More precise language for `InvocationContext.getInterceptorBindings()` method + - Clarify behavior of `InvocationContext.getInterceptorBindings()` in case of inherited/transitive bindings + +*Removals, deprecations or backwards incompatible changes:* + +- **None** + +See the following CDI section for its usage. + [#cdi-4.1] === CDI 4.1 @@ -694,7 +753,7 @@ public class OptionalELResolverExample { } ---- -==== Using EL 6.0 in JSF/Facelets +==== Using EL 6.0 in Faces/Facelets [source,xhtml] ---- @@ -1211,13 +1270,13 @@ public class ErrorHandlerServlet extends HttpServlet { [#restful-web-services-4.0] === RESTful Web Services 4.0 -Jakarta RESTful Web Services 4.0 provides a foundational API to develop web services following the Representational State Transfer (REST) architectural pattern. The goal of this release is to remove the JAXB dependency and ManagedBean support from Jakarta RESTful Web Services and add TCK tests to fill verification gaps while maintaining backward compatibility with earlier releases. +Jakarta RESTful Web Services 4.0 provides a foundational API to develop web services following the Representational State Transfer (REST) architectural pattern. The full details for this release are as follows. *New features in RESTful Web Services 4.0:* - *TCK tests for multipart/form-data API*: Added comprehensive tests for multipart form data handling - *TCK tests for default ExceptionMapper*: Added tests to verify default exception mapping behavior -- *Convenience method for checking header value lists*: New method to easily check if a header contains specific values +- *Added containsHeaderString method to a few APIs*: New method added to the APIs -ClientRequestContext, ClientResponseContext, ContainerRequestContext, ContainerResponseContext and HttpHeaders to provide an easy way to check whether a header contains specific values - *Required TCK for convenience method*: Added required tests for the new convenience method merged in PR 1066 - *Clarified JavaSE support*: Clarified JavaSE support in Section 2.3 of specification - *Added getMatchedResourceTemplate method to UriInfo*: New method to retrieve the matched resource template @@ -1513,6 +1572,21 @@ Jakarta Persistence 3.2 defines a standard for management of persistence and obj - *Query Methods*: Adds `getSingleResultOrNull()` to `Query`, `TypedQuery`, `StoredProcedureQuery` - *Named Queries*: Adds `entities()`, `classes()` and `columns()` to `NamedNativeQuery` - *Lock Mode*: Adds `lockMode()` to `EntityResult` with the default being `OPTIMISTIC` +- *Transaction Helpers*: Adds `runInTransaction()` and `callInTransaction()` convenience methods for executing code within transactions +- *EntityManager Connection Access*: Adds `runWithConnection()` and `callWithConnection()` methods for direct JDBC connection access +- *Programmatic Configuration API*: Adds `PersistenceConfiguration` for programmatic persistence unit configuration +- *Schema Management API*: Adds `SchemaManager` for schema creation, validation, truncation, and dropping +- *DDL Generation Enhancements*: Adds support for comments, check constraints, table options, and second precision in generated DDL +- *Enum Mapping Enhancements*: Adds `@EnumeratedValue` for custom enum value mapping +- *Named Query and Graph Factory Access*: Adds APIs for factory-based named query and entity graph creation/access + +*Deprecations:* +- *Temporal Types*: Deprecates usage of `Calendar`, `Date`, `Time`, `Timestamp`, `Temporal`, `MapKeyTemporal`, and `TemporalType` in favor of `java.time` API +- *multiselect Methods*: Deprecates `multiselect` methods in `CriteriaQuery` in favor of `array` or `tuple` methods defined in `CriteriaBuilder` +- *Byte[] and Character[] Arrays*: Deprecates use of `Byte[]` and `Character[]` arrays for basic attributes, in favor of primitive array types +- *Subgraph Methods for Removal*: Deprecates `addSubclassSubgraph()` in `EntityGraph` for removal; `addTreatedSubgraph()` method should be used as direct replacement +- *Attribute and Class Subgraph Methods*: Deprecates `addSubgraph(Attribute, Class)` and `addKeySubgraph()` in `Graph`/`EntityGraph`/`SubGraph` for removal +- *Transaction Methods*: Deprecates `jakarta.persistence.spi.PersistenceUnitTransactionType` and `jakarta.persistence.PersistenceUnitUtil.getTransactionType()` methods for removal [source,java] ---- @@ -1840,6 +1914,229 @@ public class ProductRepository { .getSingleResultOrNull(); // Returns null if no results } } + +// NEW in 3.2: Transaction Helpers +@Stateless +public class TransactionHelperExample { + + @PersistenceContext + private EntityManager em; + + // NEW in 3.2: runInTransaction() - Execute code within a transaction + public void processOrderWithTransaction(Order order) { + em.runInTransaction(entityManager -> { + // All operations here run in a transaction + entityManager.persist(order); + + // Update inventory + for (OrderItem item : order.getItems()) { + Product product = entityManager.find(Product.class, item.getProductId()); + product.setStockQuantity(product.getStockQuantity() - item.getQuantity()); + entityManager.merge(product); + } + + // No need to explicitly commit - handled automatically + }); + } + + // NEW in 3.2: callInTransaction() - Execute code and return a result + public Order createOrderWithTransaction(OrderRequest request) { + return em.callInTransaction(entityManager -> { + Order order = new Order(); + order.setCustomerId(request.getCustomerId()); + order.setItems(request.getItems()); + order.setTotal(calculateTotal(request.getItems())); + + entityManager.persist(order); + return order; // Return value from transaction + }); + } + + private double calculateTotal(List items) { + return items.stream() + .mapToDouble(item -> item.getPrice() * item.getQuantity()) + .sum(); + } +} + +// NEW in 3.2: EntityManager Connection Access +@Stateless +public class ConnectionAccessExample { + + @PersistenceContext + private EntityManager em; + + // NEW in 3.2: runWithConnection() - Direct JDBC access + public void executeBatchUpdate(List productIds) { + em.runWithConnection(connection -> { + String sql = "UPDATE products SET last_updated = ? WHERE id = ?"; + try (PreparedStatement stmt = connection.prepareStatement(sql)) { + Instant now = Instant.now(); + for (Long id : productIds) { + stmt.setObject(1, now); + stmt.setLong(2, id); + stmt.addBatch(); + } + stmt.executeBatch(); + } + }); + } + + // NEW in 3.2: callWithConnection() - Direct JDBC access with return value + public int getProductCount() { + return em.callWithConnection(connection -> { + String sql = "SELECT COUNT(*) FROM products WHERE active = true"; + try (PreparedStatement stmt = connection.prepareStatement(sql); + ResultSet rs = stmt.executeQuery()) { + if (rs.next()) { + return rs.getInt(1); + } + return 0; + } + }); + } +} + +// NEW in 3.2: Programmatic Configuration API +public class ProgrammaticConfigurationExample { + + public EntityManagerFactory createEntityManagerFactory() { + // NEW in 3.2: PersistenceConfiguration for programmatic setup + PersistenceConfiguration config = new PersistenceConfiguration("myPersistenceUnit"); + + config.provider("org.hibernate.jpa.HibernatePersistenceProvider") + .jtaDataSource("java:comp/env/jdbc/MyDataSource") + .managedClass(Product.class) + .managedClass(Category.class) + .managedClass(Order.class) + .property("hibernate.dialect", "org.hibernate.dialect.PostgreSQLDialect") + .property("hibernate.show_sql", "true") + .property("hibernate.format_sql", "true"); + + return Persistence.createEntityManagerFactory(config); + } +} + +// NEW in 3.2: Schema Management API +@Stateless +public class SchemaManagementExample { + + @PersistenceContext + private EntityManager em; + + public void manageSchema() { + // NEW in 3.2: SchemaManager for schema operations + SchemaManager schemaManager = em.getSchemaManager(); + + // Create schema + schemaManager.create(false); // false = don't drop existing + + // Validate schema + schemaManager.validate(); + + // Truncate all tables (useful for testing) + schemaManager.truncate(); + + // Drop schema + // schemaManager.drop(); + } +} + +// NEW in 3.2: Enum Mapping Enhancements with @EnumeratedValue +public enum OrderStatus { + @EnumeratedValue("P") + PENDING, + + @EnumeratedValue("C") + CONFIRMED, + + @EnumeratedValue("S") + SHIPPED, + + @EnumeratedValue("D") + DELIVERED, + + @EnumeratedValue("X") + CANCELLED +} + +@Entity +@Table(name = "orders") +public class Order { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + // NEW in 3.2: Custom enum mapping with @EnumeratedValue + @Enumerated(EnumType.STRING) + private OrderStatus status; // Stored as "P", "C", "S", "D", "X" in database + + private Instant orderDate; + private Double total; + + @OneToMany(mappedBy = "order", cascade = CascadeType.ALL) + private List items; + + // Constructors, getters, setters +} + +// NEW in 3.2: DDL Generation Enhancements +@Entity +@Table(name = "products", + comment = "Product catalog table", // NEW in 3.2: Table comments + options = "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4") // NEW in 3.2: Table options +@CheckConstraint(name = "price_positive", + constraint = "price > 0") // NEW in 3.2: Check constraints +public class Product { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + @Column(nullable = false, comment = "Product name") // NEW in 3.2: Column comments + private String name; + + @Column(nullable = false) + @CheckConstraint(name = "price_range", + constraint = "price BETWEEN 0.01 AND 999999.99") + private Double price; + + @Column(name = "created_at", precision = 6) // NEW in 3.2: Second precision for timestamps + private Instant createdAt; + + @Column(name = "updated_at", precision = 6) + private Instant updatedAt; + + // Constructors, getters, setters +} + +// NEW in 3.2: Named Query Factory Access +@Stateless +public class NamedQueryFactoryExample { + + @PersistenceContext + private EntityManager em; + + public void demonstrateNamedQueryFactory() { + // NEW in 3.2: Factory-based named query access + EntityManagerFactory emf = em.getEntityManagerFactory(); + + // Get named query from factory + TypedQuery query = emf.createNamedQuery("Customer.findByEmail", Customer.class); + query.setParameter("email", "user@example.com"); + Customer customer = query.getSingleResultOrNull(); + + // Get named entity graph from factory + EntityGraph graph = emf.createEntityGraph(Customer.class); + graph.addAttributeNodes("orders", "address"); + + // Use the graph in a query + List customers = em.createQuery("SELECT c FROM Customer c", Customer.class) + .setHint("jakarta.persistence.fetchgraph", graph) + .getResultList(); + } +} ---- [#pages-4.0] @@ -2607,16 +2904,46 @@ Key areas to review: - *Validation 3.1*: Leverage Java Records for validated data structures - *Concurrency 3.1*: Test async operations, especially if using Java 21 Virtual Threads +== Performance Benefits with Java 21 + +Jakarta EE 11's support for Java 21 brings significant performance improvements: + +[source,java] +---- +// Virtual Threads example with Jakarta Concurrency +@ApplicationScoped +public class VirtualThreadExample { + + @Resource + private ManagedExecutorService executor; + + public void processLargeDataset(List dataset) { + // Each task runs on a virtual thread + List> futures = dataset.stream() + .map(data -> CompletableFuture.supplyAsync( + () -> processData(data), + executor + )) + .toList(); + + // Wait for all to complete + CompletableFuture.allOf(futures.toArray(new CompletableFuture[0])) + .join(); + } + + private Result processData(Data data) { + // Processing logic + return new Result(); + } +} +---- + == Conclusion Jakarta EE 11 in Open Liberty 26.0.0.5 represents a significant advancement in enterprise Java development. The introduction of Jakarta Data 1.0 alone is a game-changer, dramatically reducing boilerplate code and improving developer productivity. Combined with updates across all major specifications, enhanced performance through Java 21 support, and a comprehensive set of modern APIs, Jakarta EE 11 provides a solid foundation for building cloud-native enterprise applications. The combination of Open Liberty's lightweight, fast runtime and Jakarta EE 11's powerful features makes this an excellent choice for organizations looking to modernize their enterprise Java applications while maintaining compatibility with industry standards. -What's more? Jakarta EE 11 also works with MicroProfile 7.0 and 7.1 in Open Liberty, allowing you to take advantage of the latest microservices features alongside the core Jakarta EE platform. Whether you're building new applications or migrating existing ones, Open Liberty and Jakarta EE 11 provide the tools and capabilities you need to succeed in today's fast-paced development environment. - -If you are using Spring Boot, Open Liberty 26.0.0.5 and later release also enables you to use Spring Boot 4.0 with Jakarta EE 11 features, providing even more flexibility in how you build your applications. - == Learn More - link:https://jakarta.ee/specifications/platform/11/[Jakarta EE 11 Specifications] From 65c0df4fc7fcf86577ab0976514d318e1a7e9b16 Mon Sep 17 00:00:00 2001 From: emijiang Date: Mon, 11 May 2026 11:25:23 +0100 Subject: [PATCH 10/26] tidy up --- ...6-05-16-Jakarta EE 11 in Open Liberty.adoc | 39 +++---------------- 1 file changed, 6 insertions(+), 33 deletions(-) diff --git a/posts/2026-05-16-Jakarta EE 11 in Open Liberty.adoc b/posts/2026-05-16-Jakarta EE 11 in Open Liberty.adoc index 8733e21ae..32a237623 100644 --- a/posts/2026-05-16-Jakarta EE 11 in Open Liberty.adoc +++ b/posts/2026-05-16-Jakarta EE 11 in Open Liberty.adoc @@ -445,13 +445,12 @@ Jakarta Annotations 3.0 defines a collection of annotations representing common ==== Migrating from @ManagedBean -If your application uses `@ManagedBean`, you must migrate to CDI managed beans. The `@ManagedBean` annotation was deprecated in Java EE 6 and has been removed in Jakarta EE 11. +If your application uses `@ManagedBean`, you must migrate to CDI managed beans. The `@ManagedBean` annotation was deprecated in an earlier release and has been removed in Jakarta EE 11. **Migration Options:** 1. **Use CDI `@Named` for JSF/EL access** - If the bean needs to be accessible from JSF pages or Expression Language 2. **Use CDI scope annotations without `@Named`** - If the bean is only used for dependency injection -3. **Use EJB annotations** - If the bean requires transaction management or other EJB services [#interceptors-2.2] === Interceptors 2.2 @@ -2897,6 +2896,7 @@ When migrating from Jakarta EE 10 to Jakarta EE 11, review the updated specifica Key areas to review: - *Jakarta Data 1.0*: Consider migrating existing DAO/repository code to Jakarta Data for improved productivity and reduced boilerplate +- *Annotations 3.0*: Migrate from `@ManagedBean` to CDI beans - *Authentication 3.1*: Remove any SecurityManager dependencies from authentication modules - *CDI 4.1*: Review dependency injection configurations, especially if using custom interceptors or producers - *Servlet 6.1*: Remove any SecurityManager dependencies; test redirect handling and error dispatches @@ -2904,39 +2904,8 @@ Key areas to review: - *Validation 3.1*: Leverage Java Records for validated data structures - *Concurrency 3.1*: Test async operations, especially if using Java 21 Virtual Threads -== Performance Benefits with Java 21 -Jakarta EE 11's support for Java 21 brings significant performance improvements: -[source,java] ----- -// Virtual Threads example with Jakarta Concurrency -@ApplicationScoped -public class VirtualThreadExample { - - @Resource - private ManagedExecutorService executor; - - public void processLargeDataset(List dataset) { - // Each task runs on a virtual thread - List> futures = dataset.stream() - .map(data -> CompletableFuture.supplyAsync( - () -> processData(data), - executor - )) - .toList(); - - // Wait for all to complete - CompletableFuture.allOf(futures.toArray(new CompletableFuture[0])) - .join(); - } - - private Result processData(Data data) { - // Processing logic - return new Result(); - } -} ----- == Conclusion @@ -2944,6 +2913,10 @@ Jakarta EE 11 in Open Liberty 26.0.0.5 represents a significant advancement in e The combination of Open Liberty's lightweight, fast runtime and Jakarta EE 11's powerful features makes this an excellent choice for organizations looking to modernize their enterprise Java applications while maintaining compatibility with industry standards. +What's more? Jakarta EE 11 also works with MicroProfile 7.0 and 7.1 in Open Liberty, allowing you to take advantage of the latest microservices features alongside the core Jakarta EE platform. Whether you're building new applications or migrating existing ones, Open Liberty and Jakarta EE 11 provide the tools and capabilities you need to succeed in today's fast-paced development environment. + +If you are using Spring Boot, Open Liberty 26.0.0.5 and later release also enables you to use Spring Boot 4.0 with Jakarta EE 11 features, providing even more flexibility in how you build your applications. + == Learn More - link:https://jakarta.ee/specifications/platform/11/[Jakarta EE 11 Specifications] From 2417fcbb2f15149e751447c0230f6ddf8b08f1fa Mon Sep 17 00:00:00 2001 From: emijiang Date: Tue, 12 May 2026 11:09:58 +0100 Subject: [PATCH 11/26] Address review comments --- ...6-05-16-Jakarta EE 11 in Open Liberty.adoc | 268 ++++++++++++------ 1 file changed, 179 insertions(+), 89 deletions(-) diff --git a/posts/2026-05-16-Jakarta EE 11 in Open Liberty.adoc b/posts/2026-05-16-Jakarta EE 11 in Open Liberty.adoc index 32a237623..72be29b6d 100644 --- a/posts/2026-05-16-Jakarta EE 11 in Open Liberty.adoc +++ b/posts/2026-05-16-Jakarta EE 11 in Open Liberty.adoc @@ -476,134 +476,226 @@ See the following CDI section for its usage. [#cdi-4.1] === CDI 4.1 -Jakarta Contexts and Dependency Injection (CDI) 4.1 specifies a means for obtaining objects in such a way as to maximize reusability, testability and maintainability compared to traditional approaches such as constructors, factories, and service locators (e.g., JNDI). +Jakarta Contexts and Dependency Injection (CDI) 4.1 brings important architectural improvements and new APIs to help framework developers build on CDI. CDI allows objects to be bound to lifecycle contexts, injected into application code, be subject to interceptors and decorators, and interact in a loosely coupled fashion via events. -*New features in CDI 4.1:* +*Key changes in CDI 4.1:* -- *Method invokers* - New API for programmatic method invocation with interceptor support -- *Executable methods* - Enhanced method execution capabilities -- *@Priority on producers* - Ability to specify priority on producer methods and fields -- *Getting interceptor bindings in standard way* - Standardized access to interceptor binding information -- *Breaking up spec/TCK* - Removed circular dependencies between specification and TCK -- *Delegate integration requirements* - Delegated integration requirements to Jakarta Platform specifications -- *Improved managed bean requirements* - Clearer wording and requirements for managed beans +- *Specification restructuring* - Integration requirements with other Jakarta EE specs moved from CDI specification to Jakarta EE Platform, Web Profile, and Core Profile specifications +- *Expression Language separation* - EL-related methods moved to new API jar (`jakarta.enterprise.cdi-el-api`) to remove CDI's dependency on EL API +- *Method Invokers* - New API allowing frameworks to call methods with CDI-managed arguments and instances +- *Interceptor binding access* - New methods on `InvocationContext` to retrieve interceptor binding annotations +- *@Priority on producers* - Producer methods and fields can now be annotated with `@Priority` for fine-grained alternative selection +- *Programmatic assignability rules* - New `BeanContainer` methods to check if beans match injection points -==== Method Invokers Example +==== Specification Restructuring -Method invokers provide a way to invoke methods programmatically while preserving CDI interceptor behavior: +One of the major changes in CDI 4.1 is the restructuring of the specification to remove circular dependencies and improve modularity: + +**Integration requirements moved to platform specs:** +Previously, CDI defined requirements for integration with other Jakarta EE specifications including Servlet, Expression Language, Enterprise Beans, Transactions, Security, Validation, and Persistence. These integration requirements have now been moved to the Jakarta EE Platform, Web Profile, and Core Profile specifications as appropriate. This makes it easier to pass the CDI TCK independently and clarifies which integrations are required at each platform level. + +**Expression Language separation:** +The CDI API previously had a direct dependency on the Expression Language (EL) API because `BeanManager` included methods that referenced EL classes. In CDI 4.1: + +- EL-related methods on `BeanManager` (`getELResolver()` and `wrapExpressionFactory()`) are deprecated for removal in CDI 5.0 +- A new supplemental API jar `jakarta.enterprise.cdi-el-api` provides `ELAwareBeanManager`, a sub-interface of `BeanManager` with the same methods +- This allows the core CDI API to remove its dependency on EL in the next major version +- Existing users will see deprecation warnings and should migrate to `ELAwareBeanManager` before CDI 5.0 + +==== Method Invokers + +Method invokers provide a way to invoke methods programmatically while allowing CDI to look up and inject some of the method parameters. This is particularly useful for frameworks that want to allow users to annotate methods and have those methods called with a mix of framework-provided and CDI-injected arguments. + +**Example use case:** An alert system where users can apply `@Alert` to methods on managed bean classes. When an alert happens, the annotated methods are called with the alert ID as the first argument, and other arguments looked up from CDI. [source,java] ---- -import jakarta.enterprise.invoke.Invoker; -import jakarta.enterprise.invoke.InvokerBuilder; -import jakarta.inject.Inject; -import jakarta.enterprise.context.ApplicationScoped; - @ApplicationScoped -public class InvokerExample { +public class MyBean { - @Inject - InvokerBuilder invokerBuilder; + @Alert + public void myAlert1(int id) { + // Do something + } - public void demonstrateInvoker() throws Exception { - // Build an invoker for a specific method - Invoker invoker = invokerBuilder - .setMethod(MyService.class.getMethod("processData", String.class)) - .build(); - - MyService service = new MyService(); - - // Invoke the method - interceptors will be applied - String result = invoker.invoke(service, new Object[]{"input data"}); - System.out.println("Result: " + result); + @Alert + public void myAlert2(int id, MyOtherBean otherBean) { + // Do something with otherBean } } +---- -@ApplicationScoped -class MyService { - - @Logged // Custom interceptor binding - public String processData(String input) { - return "Processed: " + input; +**Creating invokers** (done within a CDI extension): + +[source,java] +---- +public class InvokerExtension implements Extension { + + public static record AlertMethod(Invoker invoker, int parameterCount) { } + List alertMethods = new ArrayList<>(); + + public void createInvokers(@Observes @WithAnnotations(Alert.class) ProcessManagedBean pmb) { + for (AnnotatedMethod m : pmb.getAnnotatedBeanClass().getMethods()) { + if (m.isAnnotationPresent(Alert.class)) { + validate(m); + + InvokerBuilder> builder = pmb.createInvoker(m); + + // Look up the bean instance when invoking + builder.withInstanceLookup(); + + // Look up all arguments except the first + int parameterCount = m.getParameters().size(); + for (int i = 1; i < parameterCount; i++) { + builder.withArgumentLookup(i); + } + + alertMethods.add(new AlertMethod(builder.build(), parameterCount)); + } + } } } ---- -==== @Priority on Producers Example - -CDI 4.1 allows you to specify priority on producer methods and fields to control which producer is selected when multiple producers exist: +**Calling invokers:** [source,java] ---- -import jakarta.enterprise.inject.Produces; -import jakarta.enterprise.context.ApplicationScoped; -import jakarta.annotation.Priority; +public void invokeAlerts(int i) throws Exception { + for (AlertMethod method : alertMethods) { + // Construct an array of arguments + Object[] args = new Object[method.parameterCount]; + // First argument is the alert id + args[0] = i; + // All other arguments are looked up from CDI, so we pass in null + + // Call the method + method.invoker.invoke(null, args); + } +} +---- + +**Note:** `null` must be passed for any instance or argument that is to be looked up from CDI. In this example, the bean instance and all arguments except the first are looked up from CDI, so we pass `null` for the instance and `null` for arguments at positions 1 and beyond. +==== @Priority on Producer Methods and Fields + +Previously, a producer method or field declared as an alternative could only be enabled and have a priority assigned by putting the `@Priority` annotation on the containing bean class. If a class contained several alternative producer methods, there was no way to assign a different priority to each. + +CDI 4.1 allows the `@Priority` annotation to be placed directly on the producer field or method, enabling fine-grained control over alternative selection: + +**Before CDI 4.1** (priority on class): + +[source,java] +---- @ApplicationScoped -public class ConfigurationProducers { +@Priority(10) +public class ProducerClass { - // Default configuration with lower priority @Produces - @Priority(100) - public DatabaseConfig defaultConfig() { - DatabaseConfig config = new DatabaseConfig(); - config.setUrl("jdbc:h2:mem:testdb"); - return config; + @Alternative + public String produceString() { + return "OK"; } +} +---- + +**CDI 4.1** (priority on producer method): + +[source,java] +---- +@ApplicationScoped +public class ProducerClass { - // Production configuration with higher priority (will be selected) @Produces - @Priority(200) - public DatabaseConfig productionConfig() { - DatabaseConfig config = new DatabaseConfig(); - config.setUrl("jdbc:postgresql://prod-server:5432/proddb"); - return config; + @Alternative + @Priority(10) + public String produceString() { + return "OK"; } } +---- -class DatabaseConfig { - private String url; - public void setUrl(String url) { this.url = url; } - public String getUrl() { return url; } -} +This allows different producer methods in the same class to have different priorities, giving you more flexibility when working with alternatives. + +==== Programmatic Access to Assignability Rules + +CDI defines resolution rules that determine which beans can be injected into each injection point. Previously, there was no way to apply these rules programmatically without re-implementing the logic. CDI 4.1 adds two new methods to `BeanContainer` that implement the matching rules: + +[source,java] +---- +// Check if a bean matches an injection point +boolean isMatchingBean(Set beanTypes, + Set beanQualifiers, + Type requiredType, + Set requiredQualifiers); + +// Check if an event matches an observer +boolean isMatchingEvent(Type specifiedType, + Set specifiedQualifiers, + Type observedEventType, + Set observedEventQualifiers); ---- -==== Getting Interceptor Bindings Example +These methods allow frameworks and extensions to programmatically check whether beans or events match specific requirements using the same rules that CDI uses internally. + +==== Retrieve Interceptor Binding Information + +CDI 4.1 adds support to allow interceptors to retrieve and inspect the annotations that are used to bind them. Interceptors can now call `InvocationContext.getInterceptorBindings()` or one of the related methods to retrieve the annotations so that they can read values from them. This capability is particularly useful when you need to configure interceptor behavior based on annotation parameters. -CDI 4.1 provides a standardized way to access interceptor binding information: +For example, you might define a custom `@Logged` annotation with a parameter: [source,java] ---- -import jakarta.enterprise.inject.spi.BeanManager; -import jakarta.inject.Inject; -import jakarta.interceptor.InterceptorBinding; -import jakarta.enterprise.context.ApplicationScoped; -import java.lang.annotation.Retention; -import java.lang.annotation.Target; -import java.lang.annotation.Annotation; -import java.util.Set; -import static java.lang.annotation.ElementType.*; -import static java.lang.annotation.RetentionPolicy.RUNTIME; +@Logged("myName") +public void myMethod() { + // .... +} +---- -@InterceptorBinding -@Retention(RUNTIME) -@Target({TYPE, METHOD}) -public @interface Logged { +Then, an interceptor like this can read the `myName` value from the annotation and include it in the log message: + +[source,java] +---- +@Interceptor +@Logged("") +public class LoggedInterceptor { + + @AroundInvoke + public Object logInvocation(InvocationContext context) throws Exception { + // NEW in CDI 4.1: Retrieve interceptor bindings + Set bindings = context.getInterceptorBindings(); + + // Find the @Logged annotation and extract its value + String logName = bindings.stream() + .filter(a -> a.annotationType().equals(Logged.class)) + .map(a -> ((Logged) a).value()) + .findFirst() + .orElse("unknown"); + + System.out.println("Invoking method with log name: " + logName); + + try { + Object result = context.proceed(); + System.out.println("Method completed successfully"); + return result; + } catch (Exception e) { + System.out.println("Method failed with exception: " + e.getMessage()); + throw e; + } + } } +---- + +This annotation might be applied to a method on a managed bean like this: +[source,java] +---- @ApplicationScoped -public class InterceptorBindingExample { +public class MyService { - @Inject - BeanManager beanManager; - - public void checkInterceptorBindings() { - // Get interceptor bindings for a specific annotation - Set bindings = beanManager - .resolveInterceptorBindings(Logged.class); - - bindings.forEach(binding -> - System.out.println("Found binding: " + binding)); + @Logged("myName") + public void myMethod() { + // Business logic here } } ---- @@ -2905,8 +2997,6 @@ Key areas to review: - *Concurrency 3.1*: Test async operations, especially if using Java 21 Virtual Threads - - == Conclusion Jakarta EE 11 in Open Liberty 26.0.0.5 represents a significant advancement in enterprise Java development. The introduction of Jakarta Data 1.0 alone is a game-changer, dramatically reducing boilerplate code and improving developer productivity. Combined with updates across all major specifications, enhanced performance through Java 21 support, and a comprehensive set of modern APIs, Jakarta EE 11 provides a solid foundation for building cloud-native enterprise applications. From 681c07472e7932649c4a589b0340005d9e494100 Mon Sep 17 00:00:00 2001 From: emijiang Date: Tue, 12 May 2026 17:24:39 +0100 Subject: [PATCH 12/26] Address comment from David --- ...6-05-16-Jakarta EE 11 in Open Liberty.adoc | 313 ++++++++++++++---- 1 file changed, 256 insertions(+), 57 deletions(-) diff --git a/posts/2026-05-16-Jakarta EE 11 in Open Liberty.adoc b/posts/2026-05-16-Jakarta EE 11 in Open Liberty.adoc index 72be29b6d..e5eb7ae8c 100644 --- a/posts/2026-05-16-Jakarta EE 11 in Open Liberty.adoc +++ b/posts/2026-05-16-Jakarta EE 11 in Open Liberty.adoc @@ -1099,90 +1099,289 @@ Jakarta Security 4.0 provides an In-memory Identity Store, which is a developer- *New features in Security 4.0:* -- *Multiple HTTP Authentication Mechanisms (HAMs)* can now be defined within the same application. These mechanisms can be specified through built-in Jakarta annotations such as `@FormAuthenticationMechanismDefinition` or through custom implementations of the `HttpAuthenticationMechanism` interface. +- *Multiple HTTP Authentication Mechanisms (HAMs)*: Multiple HTTP Authentication Mechanisms can now be defined within the same application. These mechanisms can be specified through built-in Jakarta annotations such as `@FormAuthenticationMechanismDefinition` or through custom implementations of the `HttpAuthenticationMechanism` interface. Prioritisation of multiple HAMs can be managed by a custom implementation of the HttpAuthenticationMechanismHandler instead of relying on the default algorithm provided by Jakarta Security. -- *New `SecurityContext` method*: A new method is added to the `SecurityContext` interface called `getAllDeclaredCallerRoles()`, which returns a list of all static (declared) application roles that the authenticated caller is in. +- *Qualifiers for Built-in Authentication Mechanisms*: Built-in authentication mechanisms (BASIC, FORM, Custom FORM, OpenID Connect) now have qualifiers by default, whereas before they were unqualified. This enables programmatic selection and injection of specific authentication mechanisms. -- *Removed deprecated class*: References to the `IdentityStorePermission` class are removed as it was previously deprecated. +- *In-memory Identity Store*: Provides `@InMemoryIdentityStoreDefinition` annotation for defining credential stores directly in code. This is designed for testing, debugging, and demos - not recommended for production use. +- *New SecurityContext method*: A new method `getAllDeclaredCallerRoles()` is added to the `SecurityContext` interface, which returns a list of all static (declared) application roles that the authenticated caller is in. -==== Multiple HTTP Authentication Mechanisms Example +*Removals and Breaking Changes:* +- All references to the `SecurityManager` have been removed from the specification +- Built-in authentication mechanisms now have a qualifier by default, whereas before they were unqualified + +==== In-memory Identity Store + +Before the introduction of the new identity store specification, Jakarta Security natively supported only two types of identity stores: **database** and **LDAP**, both of which are used for credential validation. While effective for production environments, these options were considered heavyweight for testing, debugging, and demonstration scenarios. + +The Jakarta Security Specification 4.0 provides details on how to specify credential information to be used during the authentication workflow through the new `@InMemoryIdentityStoreDefinition` annotation: + +[source,java] +---- +. . . + +@InMemoryIdentityStoreDefinition ( + priority = 10, + priorityExpression = "${80/20}", + useFor = {VALIDATE, PROVIDE_GROUPS}, + useForExpression = "#{'VALIDATE'}", + value = { + @Credentials(callerName = "jasmine", password = "secret1", groups = { "caller", "user" } ) + } +) +---- + +All attributes for the `@InMemoryIdentityStoreDefinition` annotation are shown in the example. The `priority`, `priorityExpression`, `useFor`, and `useForExpression` attributes are optional and set to sensible defaults. + +The `@Credentials` annotation maps one or more caller names to a password and optional group values. The `callerName` and `password` attributes are mandatory. If either one is omitted, a compilation error occurs. + +The example demonstrates a single caller definition with credential information that uses a plain-text password. However, it is highly recommended that passwords be supplied using an Open Liberty-supported encoding mechanism, as illustrated in the next example: + +[source,java] +---- +@InMemoryIdentityStoreDefinition ( + value = { + @Credentials(callerName = "jasmine", password = "{xor}LDo8LTorbg==", groups = { "caller", "user" } ), + @Credentials(callerName = "frank", groups = { "user" }, password = "{hash}ARAAA Fyyw=="), + @Credentials(callerName = "sally", groups = { "user" }, password = "{aes}ARAFIYJ WRQNA==") + } +) +---- + +Encrypted and encoded passwords can be generated by using the Open Liberty `securityUtility`, which is included under the `wlp/bin/securityUtility` path. The following example demonstrates how to encode a text string by using the `xor` encoding mechanism: + +[source,bash] +---- +wlp/bin/securityUtility encode --encoding=xor +Enter text: +Re-enter text: +{xor}PTA9Lyg +---- + +==== Multiple HTTP Authentication Mechanisms + +The Jakarta Security 4.0 specification allows multiple HTTP Authentication Mechanisms (HAMs) to be defined within a single application: + +[source,java] +---- +@BasicAuthenticationMechanismDefinition(realmName="basicAuth") + +@FormAuthenticationMechanismDefinition( + loginToContinue = @LoginToContinue(errorPage = "/form-login-error.html", + loginPage = "/form-login.html")) + +@CustomFormAuthenticationMechanismDefinition( + loginToContinue = @LoginToContinue(errorPage = "/custom-login-error.html", + loginPage = "/custom-login.html")) +---- + +This example demonstrates how three HTTP Authentication Mechanisms (HAMs) can be defined within a single application. + +Custom HAMs can also be defined in the same application by implementing the `HttpAuthenticationMechanism` interface in one or more classes: + +[source,java] +---- +@ApplicationScoped +// @Priority is optional and used to control selection priority if multiple custom definitions exist +@Priority(100) +public class CustomHAM implements HttpAuthenticationMechanism { + + @Override + public AuthenticationStatus validateRequest( + HttpServletRequest request, + HttpServletResponse response, + HttpMessageContext httpMessageContext) throws AuthenticationException { + + // implement custom logic here, and return an AuthenticationStatus + return AuthenticationStatus.NOT_DONE; + } +} +---- + +So a single application can have a mix of both annotation-defined HAMs and custom ones. In the previous two snippets of code, a total of four HAMs are defined (three by annotation and one custom one). + +IMPORTANT: `@Priority` must be used to raise or lower the priority of one custom HAM over another. If not specified, then a default priority is assigned. If more than one custom HAM is defined, their priorities need to be explicitly set to unique values. If the priorities are set to the same value or remain unset and inherit the same default value, an error occurs. + +===== HAM Resolution + +An internal implementation of the Jakarta Security 4.0 `HttpAuthenticationMechanismHandler` interface (the "internal HAM handler") is provided. When an application defines multiple HAMs, this internal handler selects a single HAM to be used in the authentication flow. + +The order in which HAMs are considered (when present) is as follows: + +1. Custom (developer-provided) HAMs + * If multiple custom HAMs are defined, their relative order is resolved by using `@Priority`. +2. `OpenIdAuthenticationMechanismDefinition` +3. `CustomFormAuthenticationMechanismDefinition` +4. `FormAuthenticationMechanismDefinition` +5. `BasicAuthenticationMechanismDefinition` + +Given this ordering, the Custom HAM is always selected in the authentication workflow if all five HAM types are defined in the application. + +IMPORTANT: A developer must provide a custom implementation of the `HttpAuthenticationMechanismHandler` interface (a "custom HAM handler") if the internal HAM handler does not meet their requirements. A custom handler always takes precedence over the internal HAM handler, allowing any tailored algorithm to select a single HAM from multiple available mechanisms. + +===== Qualifiers + +HAMs - whether defined through annotations or as custom defined - can also include an optional class-level qualifier to simplify HAM injection into a custom HAM handler. For example, if you want to define qualified HAMs, you would first declare qualifier interfaces such as: + +[source,java] +---- +import static java.lang.annotation.ElementType.METHOD; +import static java.lang.annotation.ElementType.FIELD; +import static java.lang.annotation.ElementType.PARAMETER; +import static java.lang.annotation.ElementType.TYPE; +import static java.lang.annotation.RetentionPolicy.RUNTIME; + +import java.lang.annotation.Retention; +import java.lang.annotation.Target; +import jakarta.inject.Qualifier; + +@Qualifier +@Retention(RUNTIME) +@Target({TYPE, METHOD, FIELD, PARAMETER}) +public @interface Admin { +} +---- [source,java] + ---- +import static java.lang.annotation.ElementType.METHOD; +import static java.lang.annotation.ElementType.FIELD; +import static java.lang.annotation.ElementType.PARAMETER; +import static java.lang.annotation.ElementType.TYPE; +import static java.lang.annotation.RetentionPolicy.RUNTIME; + +import java.lang.annotation.Retention; +import java.lang.annotation.Target; +import jakarta.inject.Qualifier; + +@Qualifier +@Retention(RUNTIME) +@Target({TYPE, METHOD, FIELD, PARAMETER}) +public @interface User { +} +---- + +Now define multiple Basic HTTP Authentication Mechanisms in the main application: +[source,java] +---- +import Admin; +import User; + +import jakarta.security.enterprise.authentication.mechanism.http.BasicAuthenticationMechanismDefinition; +import jakarta.enterprise.context.ApplicationScoped; +import jakarta.ws.rs.ApplicationPath; +import jakarta.ws.rs.core.Application; + + +@BasicAuthenticationMechanismDefinition(realmName="admin-realm", qualifiers={Admin.class}) +@BasicAuthenticationMechanismDefinition(realmName="user-realm", qualifiers={User.class}) + +@ApplicationScoped +@ApplicationPath("/") +public class MultipleHAMsApplication extends Application { +} +---- +In the example, two Basic HTTP Authentication Mechanisms are defined in the main application. The @BasicAuthenticationMechanismDefinition annotation is used to define the realm name and the qualifier for each mechanism. The qualifiers are used to distinguish between the two mechanisms during injection. + + + +Now finally, define an implementation of the HttpAuthenticationMechanismHandler to choose which qualified HAM to use: + + +[source,java] +---- +import Admin; +import User; + +import jakarta.enterprise.context.ApplicationScoped; +import jakarta.enterprise.inject.Default; +import jakarta.inject.Inject; +import jakarta.security.enterprise.AuthenticationException; +import jakarta.security.enterprise.AuthenticationStatus; import jakarta.security.enterprise.authentication.mechanism.http.HttpAuthenticationMechanism; +import jakarta.security.enterprise.authentication.mechanism.http.HttpAuthenticationMechanismHandler; import jakarta.security.enterprise.authentication.mechanism.http.HttpMessageContext; -import jakarta.security.enterprise.credential.UsernamePasswordCredential; -import jakarta.security.enterprise.identitystore.CredentialValidationResult; -import jakarta.security.enterprise.identitystore.IdentityStoreHandler; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; -import jakarta.enterprise.context.ApplicationScoped; -import jakarta.inject.Inject; -import jakarta.security.enterprise.AuthenticationStatus; -import jakarta.security.enterprise.AuthenticationException; -import jakarta.security.enterprise.authentication.mechanism.http.FormAuthenticationMechanismDefinition; -// Define a form-based authentication mechanism using annotation -@FormAuthenticationMechanismDefinition( - loginToContinue = @LoginToContinue( - loginPage = "/login.jsp", - errorPage = "/error.jsp" - ) -) @ApplicationScoped -public class CustomAuthenticationMechanism implements HttpAuthenticationMechanism { - - @Inject - private IdentityStoreHandler identityStoreHandler; - +public class CustomHAMHandler implements HttpAuthenticationMechanismHandler { + + @Inject @Admin + private HttpAuthenticationMechanism adminHAM; + + @Inject @User + private HttpAuthenticationMechanism userHAM; + + public CustomHAMHandler() { + } + @Override - public AuthenticationStatus validateRequest( - HttpServletRequest request, - HttpServletResponse response, - HttpMessageContext context) throws AuthenticationException { - - String username = request.getParameter("username"); - String password = request.getParameter("password"); - - if (username != null && password != null) { - CredentialValidationResult result = identityStoreHandler.validate( - new UsernamePasswordCredential(username, password) - ); - - if (result.getStatus() == CredentialValidationResult.Status.VALID) { - return context.notifyContainerAboutLogin(result); - } + public AuthenticationStatus validateRequest(HttpServletRequest request, + HttpServletResponse response, + HttpMessageContext httpMessageContext) throws AuthenticationException { + String requestURI = request.getRequestURI(); + String contextPath = request.getContextPath(); + + String path = requestURI; + if (contextPath != null && !contextPath.isEmpty()) { + path = requestURI.substring(contextPath.length()); } - - return context.doNothing(); + + if (path.startsWith("/resource/admin")) { + return adminHAM.validateRequest(request, response, httpMessageContext); + } else if (path.startsWith("/resource/user")) { + return userHAM.validateRequest(request, response, httpMessageContext); + } + return AuthenticationStatus.SEND_FAILURE; } } + ---- -==== Using getAllDeclaredCallerRoles() Example +Note, you can also qualifier custom HTTP Authentication Mechanisms (as you could prior to Jakarta Security 4.0) and inject the custom HAM into your custom HAM handler. + + +==== getAllDeclaredCallerRoles() + +To use the new `SecurityContext` method, inject the `SecurityContext` implementation into your application and call the method directly: [source,java] ---- -import jakarta.inject.Inject; -import jakarta.security.enterprise.SecurityContext; -import jakarta.ws.rs.GET; -import jakarta.ws.rs.Path; -import jakarta.ws.rs.Produces; -import jakarta.ws.rs.core.MediaType; -import java.util.Set; - -@Path("/roles") -public class RolesResource { - @Inject private SecurityContext securityContext; + + Set allDeclaredCallerRoles = securityContext.getAllDeclaredCallerRoles(); + + System.out.println("All declared caller roles for caller [" + + securityContext.getCallerPrincipal().getName() + + "] are " + + allDeclaredCallerRoles.toString()); @GET + @Path("/info") @Produces(MediaType.APPLICATION_JSON) - public Set getAllRoles() { - // Get all declared roles for the authenticated caller - return securityContext.getAllDeclaredCallerRoles(); + public String getSecureInfo() { + String username = securityContext.getUserPrincipal().getName(); + boolean isAdmin = securityContext.isUserInRole("ADMIN"); + + return String.format( + "{\"user\": \"%s\", \"isAdmin\": %b}", + username, + isAdmin + ); + } + + @GET + @Path("/admin") + @Produces(MediaType.TEXT_PLAIN) + public String adminOnly() { + if (!securityContext.isUserInRole("ADMIN")) { + throw new SecurityException("Admin access required"); + } + return "Welcome, administrator!"; } } ---- From 3c72cd059d4f32450549503358009d8b9c6554dd Mon Sep 17 00:00:00 2001 From: emijiang Date: Tue, 12 May 2026 17:55:33 +0100 Subject: [PATCH 13/26] Address comments from Neena --- ...6-05-16-Jakarta EE 11 in Open Liberty.adoc | 58 ++++++++++++------- 1 file changed, 36 insertions(+), 22 deletions(-) diff --git a/posts/2026-05-16-Jakarta EE 11 in Open Liberty.adoc b/posts/2026-05-16-Jakarta EE 11 in Open Liberty.adoc index e5eb7ae8c..a6e9b4616 100644 --- a/posts/2026-05-16-Jakarta EE 11 in Open Liberty.adoc +++ b/posts/2026-05-16-Jakarta EE 11 in Open Liberty.adoc @@ -1672,7 +1672,6 @@ class Product { ---- import jakarta.ws.rs.*; import jakarta.ws.rs.core.*; -import jakarta.inject.Inject; @Path("/api/users") public class UserResource { @@ -1799,33 +1798,48 @@ class Customer { import jakarta.ws.rs.*; import jakarta.ws.rs.core.*; import java.io.InputStream; -import org.glassfish.jersey.media.multipart.FormDataParam; -import org.glassfish.jersey.media.multipart.FormDataContentDisposition; - +import java.io.IOException; +/** + * Demonstrates multipart/form-data handling in Jakarta REST 4.0 + * using the standard EntityPart API (new in REST 4.0) + */ @Path("/upload") public class FileUploadResource { - @POST @Consumes(MediaType.MULTIPART_FORM_DATA) @Produces(MediaType.APPLICATION_JSON) public Response uploadFile( - @FormDataParam("file") InputStream fileInputStream, - @FormDataParam("file") FormDataContentDisposition fileMetaData, - @FormDataParam("description") String description) { - - String fileName = fileMetaData.getFileName(); - long fileSize = fileMetaData.getSize(); - - // Process the file - System.out.println("Uploading file: " + fileName); - System.out.println("File size: " + fileSize); - System.out.println("Description: " + description); - - // Save file logic here... - - return Response.ok() - .entity(new UploadResponse(fileName, fileSize, "Upload successful")) - .build(); + @FormParam("file") EntityPart filePart, + @FormParam("description") String description) { + if (filePart == null) { + return Response.status(Response.Status.BAD_REQUEST) + .entity(new UploadResponse(null, 0, "No file provided")) + .build(); + } + try { + String fileName = filePart.getFileName().orElse("unknown"); + // Read the file content to get size + long fileSize = 0; + try (InputStream is = filePart.getContent()) { + byte[] buffer = new byte[8192]; + int bytesRead; + while ((bytesRead = is.read(buffer)) != -1) { + fileSize += bytesRead; + } + } + // Process the file + System.out.println("Uploading file: " + fileName); + System.out.println("File size: " + fileSize); + System.out.println("Description: " + description); + System.out.println("Content-Type: " + filePart.getMediaType()); + return Response.ok() + .entity(new UploadResponse(fileName, fileSize, "Upload successful")) + .build(); + } catch (IOException e) { + return Response.status(Response.Status.INTERNAL_SERVER_ERROR) + .entity(new UploadResponse(null, 0, "Error processing file: " + e.getMessage())) + .build(); + } } } From d179f9a44e39f6bc2d4a8b4450e5a7d33c0da5dc Mon Sep 17 00:00:00 2001 From: emijiang Date: Wed, 13 May 2026 16:00:31 +0100 Subject: [PATCH 14/26] Address comments from Anija --- ...6-05-16-Jakarta EE 11 in Open Liberty.adoc | 65 ++++++++++++------- 1 file changed, 42 insertions(+), 23 deletions(-) diff --git a/posts/2026-05-16-Jakarta EE 11 in Open Liberty.adoc b/posts/2026-05-16-Jakarta EE 11 in Open Liberty.adoc index a6e9b4616..7b2c57ea1 100644 --- a/posts/2026-05-16-Jakarta EE 11 in Open Liberty.adoc +++ b/posts/2026-05-16-Jakarta EE 11 in Open Liberty.adoc @@ -1918,12 +1918,21 @@ public class Customer { @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; + @Version + private Long version; + @Column(nullable = false) private String name; @Column(unique = true, nullable = false) private String email; + private String phone; + + private String status; // e.g., "ACTIVE", "INACTIVE" + + private Integer vipLevel; // VIP tier level + // NEW in 3.2: Embedded record type @Embedded private Address address; @@ -1940,6 +1949,7 @@ public class Customer { // Constructors, getters, setters } + @Stateless public class CustomerRepository { @@ -2204,20 +2214,26 @@ public class ProductRepository { .getResultList(); } - // NEW in 3.2: Combining multiple new features - public List findProductsWithComplexCriteria(Double minPrice, String namePattern) { + // NEW in 3.2: Combining multiple new features in one query + // Demonstrates: CAST, LEFT, REPLACE, ||, UNION, NULLS LAST, id() + public List findFeaturedProducts(String categoryPrefix) { return em.createQuery( + // First set: Premium products with name manipulation + "SELECT p FROM Product p " + + "WHERE CAST(p.price AS STRING) LIKE '%.99' " + // CAST operator + "AND LEFT(p.category.name, 3) = :prefix " + // LEFT function + "AND REPLACE(p.name, '-', ' ') IS NOT NULL " + // REPLACE function + "UNION " + // UNION operator + // Second set: Discounted products with concatenation "SELECT p FROM Product p " + - "WHERE p.price >= :minPrice " + - "AND REPLACE(LOWER(p.name), ' ', '') LIKE :pattern " + - "ORDER BY p.price NULLS LAST, p.name", + "WHERE (p.name || ' - ' || p.category.name) LIKE '%Sale%' " + // || concatenation + "ORDER BY p.price NULLS LAST, id(p)", // NULLS LAST + id() function Product.class ) - .setParameter("minPrice", minPrice) - .setParameter("pattern", namePattern) - .getSingleResultOrNull(); // Returns null if no results + .setParameter("prefix", categoryPrefix) + .getResultList(); // Returns list of all matching products } -} + // NEW in 3.2: Transaction Helpers @Stateless @@ -2348,20 +2364,23 @@ public class SchemaManagementExample { // NEW in 3.2: Enum Mapping Enhancements with @EnumeratedValue public enum OrderStatus { - @EnumeratedValue("P") - PENDING, - - @EnumeratedValue("C") - CONFIRMED, - - @EnumeratedValue("S") - SHIPPED, - - @EnumeratedValue("D") - DELIVERED, - - @EnumeratedValue("X") - CANCELLED + + PENDING("P"), + CONFIRMED("C"), + SHIPPED("S"), + DELIVERED("D"), + CANCELLED("X"); + + @EnumeratedValue + private final String code; + + OrderStatus(String code) { + this.code = code; + } + + public String getCode() { + return code; + } } @Entity From 317b9a7debb25a1a824a92bddd86f58a7e2ce303 Mon Sep 17 00:00:00 2001 From: emijiang Date: Wed, 13 May 2026 16:22:20 +0100 Subject: [PATCH 15/26] Address comments from Neena --- posts/2026-05-16-Jakarta EE 11 in Open Liberty.adoc | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/posts/2026-05-16-Jakarta EE 11 in Open Liberty.adoc b/posts/2026-05-16-Jakarta EE 11 in Open Liberty.adoc index 7b2c57ea1..bcbc2ad84 100644 --- a/posts/2026-05-16-Jakarta EE 11 in Open Liberty.adoc +++ b/posts/2026-05-16-Jakarta EE 11 in Open Liberty.adoc @@ -1719,6 +1719,7 @@ class Order { ---- import jakarta.ws.rs.*; import jakarta.ws.rs.core.*; +import jakarta.json.Json; import jakarta.json.JsonMergePatch; import jakarta.json.JsonValue; import jakarta.json.bind.Jsonb; @@ -1740,18 +1741,20 @@ public class CustomerResource { // NEW in REST 4.0: JSON Merge Patch support (RFC 7396) @PATCH @Path("/{id}") - @Consumes("application/merge-patch+json") + @Consumes("MediaType.APPLICATION_MERGE_PATCH_JSON") public Response patchCustomer( @PathParam("id") Long id, - JsonMergePatch patch) { + JsonValue patchJson) { try (Jsonb jsonb = JsonbBuilder.create()) { // Convert customer to JsonValue JsonValue customerJson = jsonb.fromJson( jsonb.toJson(customer), JsonValue.class); + // Create merge patch manually from the incoming JSON + JsonMergePatch mergePatch = Json.createMergePatch(patchJson); // Apply the merge patch - JsonValue patchedJson = patch.apply(customerJson); + JsonValue patchedJson = mergePatch.apply(customerJson); // Convert back to Customer object Customer patchedCustomer = jsonb.fromJson( @@ -1949,7 +1952,6 @@ public class Customer { // Constructors, getters, setters } - @Stateless public class CustomerRepository { From ce1a438989099ec279a27a9cefdd1a3cb4f1c5fa Mon Sep 17 00:00:00 2001 From: emijiang Date: Mon, 18 May 2026 09:09:54 +0100 Subject: [PATCH 16/26] Address comments from Dave --- posts/2026-05-16-Jakarta EE 11 in Open Liberty.adoc | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/posts/2026-05-16-Jakarta EE 11 in Open Liberty.adoc b/posts/2026-05-16-Jakarta EE 11 in Open Liberty.adoc index bcbc2ad84..69a03150f 100644 --- a/posts/2026-05-16-Jakarta EE 11 in Open Liberty.adoc +++ b/posts/2026-05-16-Jakarta EE 11 in Open Liberty.adoc @@ -1099,7 +1099,7 @@ Jakarta Security 4.0 provides an In-memory Identity Store, which is a developer- *New features in Security 4.0:* -- *Multiple HTTP Authentication Mechanisms (HAMs)*: Multiple HTTP Authentication Mechanisms can now be defined within the same application. These mechanisms can be specified through built-in Jakarta annotations such as `@FormAuthenticationMechanismDefinition` or through custom implementations of the `HttpAuthenticationMechanism` interface. Prioritisation of multiple HAMs can be managed by a custom implementation of the HttpAuthenticationMechanismHandler instead of relying on the default algorithm provided by Jakarta Security. +- *Multiple HTTP Authentication Mechanisms (HAMs)*: Multiple HTTP Authentication Mechanisms can now be defined within the same application. These mechanisms can be specified through built-in Jakarta annotations such as `@FormAuthenticationMechanismDefinition` or through custom implementations of the `HttpAuthenticationMechanism` interface. Prioritisation of multiple HAMs can be managed by a custom implementation of the `HttpAuthenticationMechanismHandler` instead of relying on the default algorithm provided by Jakarta Security. - *Qualifiers for Built-in Authentication Mechanisms*: Built-in authentication mechanisms (BASIC, FORM, Custom FORM, OpenID Connect) now have qualifiers by default, whereas before they were unqualified. This enables programmatic selection and injection of specific authentication mechanisms. @@ -1287,7 +1287,7 @@ In the example, two Basic HTTP Authentication Mechanisms are defined in the main -Now finally, define an implementation of the HttpAuthenticationMechanismHandler to choose which qualified HAM to use: +Now finally, define an implementation of the `HttpAuthenticationMechanismHandler` to choose which qualified HAM to use: [source,java] @@ -1341,7 +1341,7 @@ public class CustomHAMHandler implements HttpAuthenticationMechanismHandler { ---- -Note, you can also qualifier custom HTTP Authentication Mechanisms (as you could prior to Jakarta Security 4.0) and inject the custom HAM into your custom HAM handler. +Note, you can also add a qualifier to custom HTTP Authentication Mechanisms (as you could prior to Jakarta Security 4.0) and inject the custom HAM into your custom HAM handler. ==== getAllDeclaredCallerRoles() From df320a6997e3ac98edd2442fd4c13e8891729430 Mon Sep 17 00:00:00 2001 From: emijiang Date: Mon, 18 May 2026 14:47:58 +0100 Subject: [PATCH 17/26] tidy up --- posts/2026-05-16-Jakarta EE 11 in Open Liberty.adoc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/posts/2026-05-16-Jakarta EE 11 in Open Liberty.adoc b/posts/2026-05-16-Jakarta EE 11 in Open Liberty.adoc index 69a03150f..e67248f19 100644 --- a/posts/2026-05-16-Jakarta EE 11 in Open Liberty.adoc +++ b/posts/2026-05-16-Jakarta EE 11 in Open Liberty.adoc @@ -1741,7 +1741,7 @@ public class CustomerResource { // NEW in REST 4.0: JSON Merge Patch support (RFC 7396) @PATCH @Path("/{id}") - @Consumes("MediaType.APPLICATION_MERGE_PATCH_JSON") + @Consumes(MediaType.APPLICATION_MERGE_PATCH_JSON) public Response patchCustomer( @PathParam("id") Long id, JsonValue patchJson) { From d93e53758fc5dd29fc7431028bd0d6fc0deb99de Mon Sep 17 00:00:00 2001 From: emijiang Date: Mon, 18 May 2026 15:12:03 +0100 Subject: [PATCH 18/26] remove an unused field --- posts/2026-05-16-Jakarta EE 11 in Open Liberty.adoc | 3 --- 1 file changed, 3 deletions(-) diff --git a/posts/2026-05-16-Jakarta EE 11 in Open Liberty.adoc b/posts/2026-05-16-Jakarta EE 11 in Open Liberty.adoc index e67248f19..7a2d87e80 100644 --- a/posts/2026-05-16-Jakarta EE 11 in Open Liberty.adoc +++ b/posts/2026-05-16-Jakarta EE 11 in Open Liberty.adoc @@ -410,9 +410,6 @@ public class ConcurrencyExample { @Resource(lookup = "java:module/concurrent/VirtualThreadExecutor") private ManagedExecutorService executorService; - @Resource - private ManagedScheduledExecutorService scheduledExecutor; - public CompletableFuture asyncOperation() { // Leverages Virtual Threads on Java 21+ return executorService.supplyAsync(this::processData); From 7f02ef22f6c51d10919630c789e025acd447fdf5 Mon Sep 17 00:00:00 2001 From: emijiang Date: Mon, 18 May 2026 16:55:03 +0100 Subject: [PATCH 19/26] tidy up Pages 4.0 --- ...6-05-16-Jakarta EE 11 in Open Liberty.adoc | 315 ++---------------- 1 file changed, 29 insertions(+), 286 deletions(-) diff --git a/posts/2026-05-16-Jakarta EE 11 in Open Liberty.adoc b/posts/2026-05-16-Jakarta EE 11 in Open Liberty.adoc index 7a2d87e80..7e82f06a9 100644 --- a/posts/2026-05-16-Jakarta EE 11 in Open Liberty.adoc +++ b/posts/2026-05-16-Jakarta EE 11 in Open Liberty.adoc @@ -2463,317 +2463,60 @@ public class NamedQueryFactoryExample { [#pages-4.0] === Pages 4.0 +Jakarta Pages 4.0 (formerly JavaServer Pages or JSP) defines a template engine for web applications. This release removes deprecated code and provides updates necessary to align with changes in the Jakarta Servlet and Expression Language specifications. -Jakarta Pages 4.0 (formerly JavaServer Pages or JSP) defines a template engine for web applications. This release modernizes the specification by removing deprecated features and aligning with Jakarta EE 11 requirements. +*New features and enhancements in Pages 4.0:* -*New features and changes in Pages 4.0:* - -- *Removal of deprecated features*: Removed deprecated JSP 1.x style syntax and features -- *Updated namespace*: All package names updated from `javax.*` to `jakarta.*` -- *Expression Language 6.0 integration*: Full integration with the latest Expression Language specification -- *Improved error handling*: Enhanced error reporting and debugging capabilities +- *Enhanced ErrorData*: Updated [`ErrorData`](https://jakarta.ee/specifications/pages/4.0/apidocs/jakarta.servlet.jsp/jakarta/servlet/jsp/errordata) to add support for new servlet error attributes: + - `jakarta.servlet.error.query_string` - Access the query string from error pages + - `jakarta.servlet.error.method` - Access the HTTP method from error pages - *Java 17+ requirement*: Requires Java SE 17 or later as the minimum version -==== Basic JSP Page with EL 6.0 Features - -Jakarta Pages 4.0 works seamlessly with Expression Language 6.0, allowing you to use modern Java features like Records and Optional: - -[source,jsp] ----- -<%@ page contentType="text/html;charset=UTF-8" language="java" %> -<%@ taglib prefix="c" uri="jakarta.tags.core" %> - - - - Product Catalog - Jakarta Pages 4.0 - - -

Product Catalog

- - - -
-

${product.name}

-

Price: $${product.price}

- - - -

Location: ${product.address.city}, ${product.address.state}

-
- - -

Discount: ${product.discount.orElse(0)}%

- - -

Available in ${product.sizes.length} sizes

-
-
- - ----- +*Removals and deprecations:* -==== Custom Tag with Jakarta Pages 4.0 +All code deprecated as of Jakarta Server Pages 3.1 has been removed: -Creating custom tags in Jakarta Pages 4.0 with updated namespace: +- *ELResolver methods*: Removed methods that override [`ELResolver.getFeatureDescriptors()`](https://jakarta.ee/specifications/expression-language/6.0/apidocs/jakarta.el/jakarta/el/elresolver#getFeatureDescriptors()) as that method is removed in EL 6.0 +- *isThreadSafe directive*: Removed the `isThreadSafe` page directive attribute as the related Servlet API interface `SingleThreadModel` has been removed in Servlet 6.0 +- *jsp:plugin action*: Removed the `jsp:plugin` action and related actions as the associated HTML elements are no longer supported by any major browser +- *JspException.getRootCause()*: Removed deprecated [`JspException.getRootCause()`](https://jakarta.ee/specifications/pages/3.1/apidocs/jakarta.servlet.jsp/jakarta/servlet/jsp/jspexception#getRootCause()) method -[source,java] ----- -package com.example.tags; +==== Enhanced Error Handling with ErrorData -import jakarta.servlet.jsp.JspException; -import jakarta.servlet.jsp.tagext.SimpleTagSupport; -import java.io.IOException; -import java.time.LocalDateTime; -import java.time.format.DateTimeFormatter; - -// NEW in Pages 4.0: Using jakarta.servlet.jsp namespace -public class FormatDateTag extends SimpleTagSupport { - - private LocalDateTime date; - private String pattern = "yyyy-MM-dd HH:mm:ss"; - - public void setDate(LocalDateTime date) { - this.date = date; - } - - public void setPattern(String pattern) { - this.pattern = pattern; - } - - @Override - public void doTag() throws JspException, IOException { - if (date != null) { - DateTimeFormatter formatter = DateTimeFormatter.ofPattern(pattern); - getJspContext().getOut().write(date.format(formatter)); - } - } -} ----- - -Tag Library Descriptor (TLD) file: - -[source,xml] ----- - - - - 1.0 - custom - http://example.com/tags - - - formatDate - com.example.tags.FormatDateTag - empty - - date - true - true - java.time.LocalDateTime - - - pattern - false - true - - - ----- - -==== Using Custom Tags in JSP +Jakarta Pages 4.0 adds new error attributes to [`ErrorData`](https://jakarta.ee/specifications/pages/4.0/apidocs/jakarta.servlet.jsp/jakarta/servlet/jsp/errordata) for better error page diagnostics: [source,jsp] ---- -<%@ page contentType="text/html;charset=UTF-8" language="java" %> -<%@ taglib prefix="c" uri="jakarta.tags.core" %> -<%@ taglib prefix="custom" uri="http://example.com/tags" %> +<%@ page contentType="text/html;charset=UTF-8" language="java" isErrorPage="true" %> - Order Details + Error Page - Jakarta Pages 4.0 -

Order #${order.id}

- - -

Order Date:

-

Delivery Date:

- -

Items

- - - - - - - - - - - - - - - - - - - -
ProductQuantityPriceTotal
${item.product.name}${item.quantity}$${item.product.price}$${item.quantity * item.product.price}
- - -

Total: $${order.total} (${order.items.length} ${order.items.length == 1 ? 'item' : 'items'})

- - ----- - -==== JSP with CDI Integration - -Jakarta Pages 4.0 integrates seamlessly with CDI 4.1: - -[source,java] ----- -package com.example.beans; - -import jakarta.enterprise.context.RequestScoped; -import jakarta.inject.Named; -import jakarta.inject.Inject; -import java.util.List; - -@Named -@RequestScoped -public class ProductBean { - - @Inject - private ProductService productService; - - private String searchTerm; - private List searchResults; - - public void search() { - if (searchTerm != null && !searchTerm.isEmpty()) { - searchResults = productService.searchProducts(searchTerm); - } - } +

An Error Occurred

- // Getters and setters - public String getSearchTerm() { - return searchTerm; - } + +

Status Code: ${pageContext.errorData.statusCode}

+

Request URI: ${pageContext.errorData.requestURI}

+

Servlet Name: ${pageContext.errorData.servletName}

- public void setSearchTerm(String searchTerm) { - this.searchTerm = searchTerm; - } + +

HTTP Method: ${pageContext.request.getAttribute('jakarta.servlet.error.method')}

- public List getSearchResults() { - return searchResults; - } -} ----- - -JSP page using the CDI bean: - -[source,jsp] ----- -<%@ page contentType="text/html;charset=UTF-8" language="java" %> -<%@ taglib prefix="c" uri="jakarta.tags.core" %> - - - - Product Search - - -

Search Products

- -
- - -
- - -

Search Results (${productBean.searchResults.length} found)

-
    - -
  • - ${product.name} - $${product.price} - - Only ${product.stockQuantity} left! - -
  • -
    -
-
+ +

Query String: ${pageContext.request.getAttribute('jakarta.servlet.error.query_string')}

- -

No products found matching "${productBean.searchTerm}"

+ + +

Exception Details

+

Type: ${pageContext.errorData.throwable.class.name}

+

Message: ${pageContext.errorData.throwable.message}

---- -==== Error Handling in Jakarta Pages 4.0 - -Improved error handling with custom error pages: - -[source,jsp] ----- -<%@ page contentType="text/html;charset=UTF-8" language="java" isErrorPage="true" %> -<%@ taglib prefix="c" uri="jakarta.tags.core" %> - - - - Error - ${pageContext.errorData.statusCode} - - -

An Error Occurred

- -
-

Status Code: ${pageContext.errorData.statusCode}

-

Request URI: ${pageContext.errorData.requestURI}

- - -

Exception: ${pageContext.exception.class.name}

-

Message: ${pageContext.exception.message}

-
-
- -

Return to Home

- - ----- - -Configure error page in `web.xml`: - -[source,xml] ----- - - - - - 404 - /error.jsp - - - - 500 - /error.jsp - - - - java.lang.Exception - /error.jsp - - ----- [#websocket-2.2] === WebSocket 2.2 From a48787b6a4d7b9ec02c82f3d453364f79bf1e4e1 Mon Sep 17 00:00:00 2001 From: emijiang Date: Wed, 20 May 2026 11:20:09 +0100 Subject: [PATCH 20/26] Tidy up and address the various comments --- ...6-05-16-Jakarta EE 11 in Open Liberty.adoc | 301 +++++++++--------- 1 file changed, 146 insertions(+), 155 deletions(-) diff --git a/posts/2026-05-16-Jakarta EE 11 in Open Liberty.adoc b/posts/2026-05-16-Jakarta EE 11 in Open Liberty.adoc index 7e82f06a9..10307c61d 100644 --- a/posts/2026-05-16-Jakarta EE 11 in Open Liberty.adoc +++ b/posts/2026-05-16-Jakarta EE 11 in Open Liberty.adoc @@ -20,7 +20,7 @@ Emily Jiang :toc-title: Table of Contents :toclevels: 3 -link:https://jakarta.ee/release/11/[Jakarta EE 11] marks a significant milestone in the evolution of enterprise Java development. This release delivers enhanced developer productivity, improved performance, and modernized APIs that align with the Java LTS releases: Java SE 17, Java SE 21. For more information, see the https://jakarta.ee/specifications/platform/11/[Jakarta EE Platform 11 specification] and https://jakarta.ee/specifications/pages/4.0/[Jakarta Pages 4.0 specification]. +link:https://jakarta.ee/release/11/[Jakarta EE 11] marks a significant milestone in the evolution of enterprise Java development. This release delivers enhanced developer productivity, improved performance, and modernized APIs that align with the Java LTS releases: Java SE 17, Java SE 21. For more information, see the https://jakarta.ee/specifications/platform/11/[Jakarta EE Platform 11 specification]. == What's New in Jakarta EE 11? @@ -33,7 +33,7 @@ Jakarta EE 11 represents a major step forward for cloud-native enterprise Java a * <> * <> * <> -* <> +* <> * <> * <> * <> @@ -207,12 +207,13 @@ public class ProductResource { [#authentication-3.1] === Authentication 3.1 -Jakarta Authentication 3.1 defines a general low-level SPI for authentication mechanisms, which are controllers that interact with a caller and a container's environment to obtain the caller's credentials, validate these, and pass an authenticated identity (such as name and groups) to the container. +Jakarta Authentication defines a general low-level SPI for authentication mechanisms, which are controllers that interact with a caller and a container's environment to obtain the caller's credentials, validate these, and pass an authenticated identity (such as name and groups) to the container. *Key changes in Authentication 3.1:* -- Removes references to the SecurityManager (aligned with Java's deprecation and removal of SecurityManager) -- Evolves the API in a smaller way to support the overall goals of Jakarta Security -- Consists of several profiles, with each profile telling how a specific container (such as Jakarta Servlet) can integrate with and adapt to this SPI + + - Removes references to the SecurityManager (aligned with Java's deprecation and removal of SecurityManager) + - Evolves the API in a smaller way to support the overall goals of Jakarta Security + - Consists of several profiles, with each profile telling how a specific container (such as Jakarta Servlet) can integrate with and adapt to this SPI The 3.1 version removes the deprecated Permission-related fields in the `jakarta.security.auth.message.config.AuthConfigFactory` class. The methods in the class no longer do permission checking. These changes are part of Jakarta EE 11's removal of SecurityManager support. @@ -322,11 +323,23 @@ public class CustomServerAuthModule implements ServerAuthModule { [#authorization-3.0] === Authorization 3.0 -Jakarta Authorization 3.0 defines an SPI for authorization modules, which are repositories of permissions that facilitate subject-based security by determining whether a subject has a specific permission. +Jakarta Authorization defines an SPI for authorization modules, which are repositories of permissions that facilitate subject-based security by determining whether a subject has a specific permission. + +*What's New in Authorization 3.0:* + +- **New `PolicyFactory` and `Policy` classes**: Introduces `jakarta.security.jacc.PolicyFactory` and `jakarta.security.jacc.Policy` as replacements for the deprecated `java.security.Policy` class. With the new `PolicyFactory` API, you can now have a `Policy` per policy context instead of a global policy, allowing separate policies to be maintained for each application. + +- **Programmatic policy provider registration**: Adds the ability to register policy providers programmatically on a per-application basis, making Jakarta Authorization more suitable for cloud deployments. This mirrors the API available in Jakarta Authentication. + +- **Standardized context ID for Servlet containers**: Defines a standard format for context IDs used in Servlet container environments. -The 3.0 API introduces the new `jakarta.security.jacc.PolicyFactory` and `jakarta.security.jacc.Policy` classes for doing authorization decisions. These classes are added to remove the dependency on the `java.security.Policy` class, which is deprecated in newer versions of Java. With the new `PolicyFactory` API, you can now have a `Policy` per policy context instead of having a global policy. This design allows separate policies to be maintained for each application. +- **Convenience methods**: Adds several convenience methods to make the API easier to use. -Additionally, the 3.0 specification defines a mechanism to define `PolicyConfigurationFactory` and `PolicyFactory` classes in your application by using a `web.xml` file. This design allows for an application to have a different configuration than the server-scoped one, but still allow for it to delegate to a server scoped factory for any other applications. Authorization modules can do this delegation by using decorator constructors for both `PolicyConfigurationFactory` and `PolicyFactory` classes. +*Removals and Changes in Authorization 3.0:* + +- **Removal of `java.security.Policy` dependency**: Eliminates dependency on the deprecated `java.security.Policy` and `java.security.SecurityManager` classes, aligning with Java SE's deprecation and removal of SecurityManager. + +- **Breaking API changes**: This is a major breaking update. Applications using Jakarta Authorization 2.x will need to migrate to the new `PolicyFactory` and `Policy` classes. To configure your authorization modules in your application's `web.xml` file, add specification defined context parameters: @@ -388,7 +401,14 @@ public boolean impliesByRole(Permission p, Subject subject) { [#concurrency-3.1] === Concurrency 3.1 -Enhanced concurrency utilities with better Virtual Thread support for Java 21+. +Jakarta Concurrency provides asynchronous capabilities to Jakarta EE application components. Jakarta Concurrency 3.1 provides enhanced concurrency utilities with several new features and improvements: + +* **Integration with Java 21 Virtual Threads** - Native support for virtual threads to improve scalability +* **Java Flow/ReactiveStreams and context propagation** - Better integration with reactive programming models +* **Replace more features from EJB** - Migrated scheduling and asynchronous capabilities (such as `@Schedule` and `@Asynchronous` annotations) +* **Become more CDI-centric** - Improved integration with Jakarta CDI +* **Specification bug fixes and clarifications** - Enhanced clarity and resolved ambiguities +* **TCK fixes and enhancements** - Improved test coverage and reliability [source,java] ---- @@ -419,7 +439,7 @@ public class ConcurrencyExample { return "Processed data"; } @Asynchronous( - executor = "java:module/concurrent/VirtualExecutor", + executor = "java:module/concurrent/VirtualThreadExecutor", runAt = @Schedule(cron = "0 3 * * *")) // daily at 3 AM private void performMaintenanceTask(@Observes Startup event) { // Maintenance logic @@ -430,11 +450,7 @@ public class ConcurrencyExample { [#annotations-3.0] === Annotations 3.0 -Jakarta Annotations 3.0 defines a collection of annotations representing common semantic concepts that enable a declarative style of programming in the Jakarta EE platform. - -*What's New in Annotations 3.0:* - -- **N/A** - No new features, enhancements, or additions +Jakarta Annotations defines a collection of annotations representing common semantic concepts that enable a declarative style of programming in the Jakarta EE platform. *Changes in Annotations 3.0:* @@ -452,17 +468,13 @@ If your application uses `@ManagedBean`, you must migrate to CDI managed beans. [#interceptors-2.2] === Interceptors 2.2 -Jakarta Interceptors 2.2 defines a means of interposing on business method invocations and specific events in the lifecycle of beans. +Jakarta Interceptors defines a means of interposing on business method invocations and specific events in the lifecycle of beans. *New features, enhancements or additions:* - **Updated dependencies for Jakarta EE 11** - Jakarta Annotations to 3.0.0 -- **Add standard accessor to interceptor bindings** - Provides a standard way to access interceptor bindings from `InvocationContext` - **Provide access to interceptor bindings from InvocationContext** - New method `getInterceptorBindings()` added to `InvocationContext` interface -- **Improve InvocationContext.getInterceptorBindings() language** - - More precise language for `InvocationContext.getInterceptorBindings()` method - - Clarify behavior of `InvocationContext.getInterceptorBindings()` in case of inherited/transitive bindings *Removals, deprecations or backwards incompatible changes:* @@ -473,7 +485,7 @@ See the following CDI section for its usage. [#cdi-4.1] === CDI 4.1 -Jakarta Contexts and Dependency Injection (CDI) 4.1 brings important architectural improvements and new APIs to help framework developers build on CDI. CDI allows objects to be bound to lifecycle contexts, injected into application code, be subject to interceptors and decorators, and interact in a loosely coupled fashion via events. +Jakarta Contexts and Dependency Injection (CDI) specifies a means for obtaining objects in such a way as to maximize reusability, testability and maintainability compared to traditional approaches such as constructors, factories, and service locators (e.g., JNDI). CDI 4.1 brings important architectural improvements and new APIs to help framework developers build on CDI. CDI allows objects to be bound to lifecycle contexts, injected into application code, be subject to interceptors and decorators, and interact in a loosely coupled fashion via events. *Key changes in CDI 4.1:* @@ -499,6 +511,70 @@ The CDI API previously had a direct dependency on the Expression Language (EL) A - This allows the core CDI API to remove its dependency on EL in the next major version - Existing users will see deprecation warnings and should migrate to `ELAwareBeanManager` before CDI 5.0 +==== Retrieve Interceptor Binding Information + +CDI 4.1 adds support to allow interceptors to retrieve and inspect the annotations that are used to bind them. Interceptors can now call `InvocationContext.getInterceptorBindings()` or one of the related methods to retrieve the annotations so that they can read values from them. This capability is particularly useful when you need to configure interceptor behavior based on annotation parameters. + +For example, you might define a custom `@Logged` annotation with a parameter: + +[source,java] +---- +import jakarta.enterprise.util.Nonbinding; +import jakarta.interceptor.InterceptorBinding; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +@InterceptorBinding +@Target({ElementType.TYPE, ElementType.METHOD}) +@Retention(RetentionPolicy.RUNTIME) +public @interface Logged { + @Nonbinding + String value(); +} +---- + +Then apply it to a method: + +[source,java] +---- +@Logged("myName") +public void myMethod() { + // .... +} +---- + +An interceptor like this can read the `myName` value from the annotation and include it in the log message: + +[source,java] +---- +@Interceptor +@Logged("") +public class LoggedInterceptor { + + @AroundInvoke + public Object logInvocation(InvocationContext context) throws Exception { + // NEW in CDI 4.1: Retrieve interceptor bindings + Set bindings = context.getInterceptorBindings(); + + // Find the @Logged annotation and extract its value + String logName = context.getInterceptorBinding(Logged.class).value(); + + System.out.println("Invoking method with log name: " + logName); + + try { + Object result = context.proceed(); + System.out.println("Method completed successfully"); + return result; + } catch (Exception e) { + System.out.println("Method failed with exception: " + e.getMessage()); + throw e; + } + } +} +---- + ==== Method Invokers Method invokers provide a way to invoke methods programmatically while allowing CDI to look up and inject some of the method parameters. This is particularly useful for frameworks that want to allow users to annotate methods and have those methods called with a mix of framework-provided and CDI-injected arguments. @@ -635,72 +711,11 @@ boolean isMatchingEvent(Type specifiedType, These methods allow frameworks and extensions to programmatically check whether beans or events match specific requirements using the same rules that CDI uses internally. -==== Retrieve Interceptor Binding Information - -CDI 4.1 adds support to allow interceptors to retrieve and inspect the annotations that are used to bind them. Interceptors can now call `InvocationContext.getInterceptorBindings()` or one of the related methods to retrieve the annotations so that they can read values from them. This capability is particularly useful when you need to configure interceptor behavior based on annotation parameters. - -For example, you might define a custom `@Logged` annotation with a parameter: - -[source,java] ----- -@Logged("myName") -public void myMethod() { - // .... -} ----- - -Then, an interceptor like this can read the `myName` value from the annotation and include it in the log message: - -[source,java] ----- -@Interceptor -@Logged("") -public class LoggedInterceptor { - - @AroundInvoke - public Object logInvocation(InvocationContext context) throws Exception { - // NEW in CDI 4.1: Retrieve interceptor bindings - Set bindings = context.getInterceptorBindings(); - - // Find the @Logged annotation and extract its value - String logName = bindings.stream() - .filter(a -> a.annotationType().equals(Logged.class)) - .map(a -> ((Logged) a).value()) - .findFirst() - .orElse("unknown"); - - System.out.println("Invoking method with log name: " + logName); - - try { - Object result = context.proceed(); - System.out.println("Method completed successfully"); - return result; - } catch (Exception e) { - System.out.println("Method failed with exception: " + e.getMessage()); - throw e; - } - } -} ----- - -This annotation might be applied to a method on a managed bean like this: - -[source,java] ----- -@ApplicationScoped -public class MyService { - - @Logged("myName") - public void myMethod() { - // Business logic here - } -} ----- [#expression-language-6.0] === Expression Language 6.0 -Jakarta Expression Language 6.0 defines an expression language for Java applications. This release makes the dependency on the java.desktop module optional, removes references to the SecurityManager, and provides a small number of usability improvements. +Jakarta Expression Language defines an expression language for Java applications. This release makes the dependency on the java.desktop module optional, removes references to the SecurityManager, and provides a small number of usability improvements. *New features in Expression Language 6.0:* @@ -867,7 +882,7 @@ public class OptionalELResolverExample { [#faces-4.1] === Faces 4.1 -Jakarta Server Faces 4.1 defines an MVC framework for building user interfaces for web applications, including UI components, state management, event handling, input validation, page navigation, and support for internationalization and accessibility. This release removes references to the SecurityManager, further aligns with CDI where possible, and provides various small enhancements and clarifications. +Jakarta Faces defines an MVC framework for building user interfaces for web applications, including UI components, state management, event handling, input validation, page navigation, and support for internationalization and accessibility. This release removes references to the SecurityManager, further aligns with CDI where possible, and provides various small enhancements and clarifications. *New features in Faces 4.1:* @@ -1092,6 +1107,8 @@ public class MessageBean { [#security-4.0] === Security 4.0 +Jakarta Security is the overarching API designed to provide a holistic, vendor-neutral security model for the Jakarta EE platform. It simplifies authentication and authorization by leveraging CDI and annotations to manage three core artifacts: Authentication Mechanisms, Identity Stores, and Permission Stores. + Jakarta Security 4.0 provides an In-memory Identity Store, which is a developer-defined store of credential information that is used during the Open Liberty authentication and authorization work flow. It provides a quick, simple, and convenient authentication mechanism for Liberty application testing, debugging, demos, and more. *New features in Security 4.0:* @@ -1386,9 +1403,10 @@ To use the new `SecurityContext` method, inject the `SecurityContext` implementa [#servlet-6.1] === Servlet 6.1 -Jakarta Servlet 6.1 defines a server-side API for handling HTTP requests and responses. This release removes references to the SecurityManager and provides various small enhancements and clarifications. +Jakarta Servlet defines a server-side API for handling HTTP requests and responses. This release removes references to the SecurityManager and provides various small enhancements and clarifications. *New features in Servlet 6.1:* + - Allow control of status code and response body when sending a redirect - Add a query string attribute to error dispatches - Add constants for new HTTP status codes @@ -1397,6 +1415,7 @@ Jakarta Servlet 6.1 defines a server-side API for handling HTTP requests and res - Various clarifications throughout the specification *Removals:* + - All references to the SecurityManager and associated APIs have been removed ==== Custom Redirect with Status Code Control @@ -1557,7 +1576,7 @@ public class ErrorHandlerServlet extends HttpServlet { [#restful-web-services-4.0] === RESTful Web Services 4.0 -Jakarta RESTful Web Services 4.0 provides a foundational API to develop web services following the Representational State Transfer (REST) architectural pattern. The full details for this release are as follows. +Jakarta RESTful Web Services provides a foundational API to develop web services following the Representational State Transfer (REST) architectural pattern. The full details for this release are as follows. *New features in RESTful Web Services 4.0:* @@ -1864,9 +1883,10 @@ class UploadResponse { [#persistence-3.2] === Persistence 3.2 -Jakarta Persistence 3.2 defines a standard for management of persistence and object/relational mapping in Java environments. +Jakarta Persistence defines a standard for management of persistence and object/relational mapping in Java environments. *New features in Persistence 3.2:* + - *Java Record Support*: Adds support for Java record types as embeddable classes - *java.time Support*: Adds support for `java.time.Instant` and `java.time.Year` with clarified JDBC mappings for basic types - *New Query Operators*: Adds `union`, `intersect`, `except`, `cast`, `left`, `right`, and `replace` operators for Jakarta Persistence QL and criteria queries @@ -1885,6 +1905,7 @@ Jakarta Persistence 3.2 defines a standard for management of persistence and obj - *Named Query and Graph Factory Access*: Adds APIs for factory-based named query and entity graph creation/access *Deprecations:* + - *Temporal Types*: Deprecates usage of `Calendar`, `Date`, `Time`, `Timestamp`, `Temporal`, `MapKeyTemporal`, and `TemporalType` in favor of `java.time` API - *multiselect Methods*: Deprecates `multiselect` methods in `CriteriaQuery` in favor of `array` or `tuple` methods defined in `CriteriaBuilder` - *Byte[] and Character[] Arrays*: Deprecates use of `Byte[]` and `Character[]` arrays for basic attributes, in favor of primitive array types @@ -2463,7 +2484,7 @@ public class NamedQueryFactoryExample { [#pages-4.0] === Pages 4.0 -Jakarta Pages 4.0 (formerly JavaServer Pages or JSP) defines a template engine for web applications. This release removes deprecated code and provides updates necessary to align with changes in the Jakarta Servlet and Expression Language specifications. +Jakarta Pages defines a template engine for web applications. This release removes deprecated code and provides updates necessary to align with changes in the Jakarta Servlet and Expression Language specifications. *New features and enhancements in Pages 4.0:* @@ -2521,7 +2542,7 @@ Jakarta Pages 4.0 adds new error attributes to [`ErrorData`](https://jakarta.ee/ [#websocket-2.2] === WebSocket 2.2 -Jakarta WebSocket 2.2 defines an API for Server and Client Endpoints for the WebSocket protocol (RFC6455). This release removes references to the SecurityManager and provides some minor updates and clarifications. +Jakarta WebSocket defines an API for Server and Client Endpoints for the WebSocket protocol (RFC6455). This release removes references to the SecurityManager and provides some minor updates and clarifications. *New features in WebSocket 2.2:* @@ -2695,9 +2716,10 @@ public class PingPongWebSocket { [#validation-3.1] === Validation 3.1 -Jakarta Validation 3.1 defines a metadata model and API for JavaBean and method validation. This release is targeting Jakarta EE 11 and has clarified support for Records introduced by JEP 395. +Jakarta Validation defines a metadata model and API for JavaBean and method validation. This release has clarified support for Records introduced by JEP 395. *Key changes in Validation 3.1:* + - Clarify Java Records support for validation - Update dependencies for Jakarta EE 11 - No removals, deprecations, or backwards incompatible changes @@ -2852,20 +2874,7 @@ To use Jakarta EE 11 features in Open Liberty 26.0.0.5, you can enable the Jakar jakartaee-11.0 - - - - - - - - - + ... ---- @@ -2875,13 +2884,14 @@ You can also enable individual features as needed: [source,xml] ---- - servlet-6.1 - cdi-4.1 - persistence-3.2 - faces-4.1 - data-1.0 - websocket-2.2 - validation-3.1 + jakartaee-11.0 + servlet + cdi + persistence + faces + data + websocket + validation ---- @@ -2910,57 +2920,34 @@ Add Jakarta EE 11 dependencies to your `pom.xml`: ---- -== Summary of Key Enhancements by Specification - -Based on the official Jakarta EE 11 specifications: - -=== Jakarta Data 1.0 (NEW) -- Repository pattern for data access -- Query by method name -- Pagination support -- Platform integrations with CDI, Persistence, NoSQL, Transactions, and Validation -- Multiple profile support (standalone, core, web, platform) - -=== CDI 4.1 -- Breaking up spec/TCK to remove circular dependencies -- Method invokers and executable methods -- Getting interceptor bindings in standard way -- @Priority on producers -- Programmatic access to Assignability rules - -=== Authentication 3.1 -- Removes references to the SecurityManager -- Evolves the API to support Jakarta Security goals -- Consists of several profiles for different containers (Jakarta Servlet, etc.) -- Low-level SPI for authentication mechanisms - -=== Servlet 6.1 -- Control of status code and response body when sending redirects -- Query string attribute to error dispatches -- New HTTP status code constants -- Charset-based overloaded methods -- ByteBuffer support for ServletInputStream and ServletOutputStream -- *Removed*: All SecurityManager references +== Integration with MicroProfile -=== Persistence 3.2 -- Java record types as embeddable classes -- java.time.Instant and java.time.Year support -- New query operators: union, intersect, except, cast, left, right, replace -- String concatenation operator (||) -- Null precedence in ordering (NULLS FIRST/LAST) -- getSingleResultOrNull() method -- CriteriaSelect and enhanced Criteria API +This Open Liberty release provides a great ecosystem of enabling Jakarta EE 11 working with MicroProfile 7.0 and 7.1, allowing you to take advantage of the latest microservices features. You can enable MicroProfile and Jakarta EE 11 features in your `server.xml` as needed: +[source,xml] +---- + + microprofile-7.1 + jakartaee-11.0 + -=== Validation 3.1 -- Clarified support for Java Records -- Updated dependencies for Jakarta EE 11 -- No removals, deprecations, or backwards incompatible changes +---- + +== Spring Boot 4.0 +Open Liberty also enables Spring Boot 4.0 applications, allowing you to leverage the latest features and improvements in the Jakarta EE 11 release. You can enable Spring Boot 4.0 feature in your `server.xml`: +[source,xml] +---- + + springBoot-4.0 + servlet-6.1 + +---- == Migration Considerations When migrating from Jakarta EE 10 to Jakarta EE 11, review the updated specifications to understand the changes and enhancements. Most applications should migrate smoothly, but it's important to test thoroughly, especially if you're using features that have been updated. Key areas to review: + - *Jakarta Data 1.0*: Consider migrating existing DAO/repository code to Jakarta Data for improved productivity and reduced boilerplate - *Annotations 3.0*: Migrate from `@ManagedBean` to CDI beans - *Authentication 3.1*: Remove any SecurityManager dependencies from authentication modules @@ -2981,6 +2968,10 @@ What's more? Jakarta EE 11 also works with MicroProfile 7.0 and 7.1 in Open Libe If you are using Spring Boot, Open Liberty 26.0.0.5 and later release also enables you to use Spring Boot 4.0 with Jakarta EE 11 features, providing even more flexibility in how you build your applications. +== Acknowledgements + +Many thanks to the Jakarta EE for their hard work in bringing Jakarta EE 11 to life and Open Liberty teams ensuring it runs smoothly on Open Liberty. The contributions from the community, including feedback, testing, and code contributions, have been invaluable in making this release a success. I also want to thank my colleagues in the Open Liberty team who provided valuable feedback to this blog: Andrew Rouse, Anija K A, David Webster, Jared Anderson, Nathan Rauh, Paul Nicolucci, Mark Swatosh. Kyle Aure, Jim Kurger, Neena P Jacob, Volodymyre Siedlecki, Phu Dinh and many others. + == Learn More - link:https://jakarta.ee/specifications/platform/11/[Jakarta EE 11 Specifications] From 665b5f9ee3beaa07fe82d7d4ef4728541d67304e Mon Sep 17 00:00:00 2001 From: emijiang Date: Wed, 20 May 2026 17:18:39 +0100 Subject: [PATCH 21/26] address comments from Andrew --- posts/2026-05-16-Jakarta EE 11 in Open Liberty.adoc | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/posts/2026-05-16-Jakarta EE 11 in Open Liberty.adoc b/posts/2026-05-16-Jakarta EE 11 in Open Liberty.adoc index 10307c61d..18bc0d671 100644 --- a/posts/2026-05-16-Jakarta EE 11 in Open Liberty.adoc +++ b/posts/2026-05-16-Jakarta EE 11 in Open Liberty.adoc @@ -491,8 +491,8 @@ Jakarta Contexts and Dependency Injection (CDI) specifies a means for obtaining - *Specification restructuring* - Integration requirements with other Jakarta EE specs moved from CDI specification to Jakarta EE Platform, Web Profile, and Core Profile specifications - *Expression Language separation* - EL-related methods moved to new API jar (`jakarta.enterprise.cdi-el-api`) to remove CDI's dependency on EL API -- *Method Invokers* - New API allowing frameworks to call methods with CDI-managed arguments and instances - *Interceptor binding access* - New methods on `InvocationContext` to retrieve interceptor binding annotations +- *Method Invokers* - New API allowing frameworks to call methods with CDI-managed arguments and instances - *@Priority on producers* - Producer methods and fields can now be annotated with `@Priority` for fine-grained alternative selection - *Programmatic assignability rules* - New `BeanContainer` methods to check if beans match injection points @@ -555,10 +555,7 @@ public class LoggedInterceptor { @AroundInvoke public Object logInvocation(InvocationContext context) throws Exception { - // NEW in CDI 4.1: Retrieve interceptor bindings - Set bindings = context.getInterceptorBindings(); - - // Find the @Logged annotation and extract its value + // NEW in CDI 4.1: Find the @Logged annotation and extract its value String logName = context.getInterceptorBinding(Logged.class).value(); System.out.println("Invoking method with log name: " + logName); @@ -2970,7 +2967,7 @@ If you are using Spring Boot, Open Liberty 26.0.0.5 and later release also enabl == Acknowledgements -Many thanks to the Jakarta EE for their hard work in bringing Jakarta EE 11 to life and Open Liberty teams ensuring it runs smoothly on Open Liberty. The contributions from the community, including feedback, testing, and code contributions, have been invaluable in making this release a success. I also want to thank my colleagues in the Open Liberty team who provided valuable feedback to this blog: Andrew Rouse, Anija K A, David Webster, Jared Anderson, Nathan Rauh, Paul Nicolucci, Mark Swatosh. Kyle Aure, Jim Kurger, Neena P Jacob, Volodymyre Siedlecki, Phu Dinh and many others. +Many thanks to the Jakarta EE for their hard work in bringing Jakarta EE 11 to life and Open Liberty teams ensuring it runs smoothly on Open Liberty. The contributions from the community, including feedback, testing, and code contributions, have been invaluable in making this release a success. I also want to thank my colleagues in the Open Liberty team who provided valuable feedback to this blog: Andrew Rouse, Anija K A, David Webster, Jared Anderson, Nathan Rauh, Paul Nicolucci, Mark Swatosh, Kyle Aure, Jim Kurger, Neena P Jacob, Volodymyre Siedlecki, Phu Dinh and many others. == Learn More From ab714ca55c2c363c85d7ce5306ec3e202455bff2 Mon Sep 17 00:00:00 2001 From: emijiang Date: Wed, 20 May 2026 18:57:49 +0100 Subject: [PATCH 22/26] Address comments from Jared --- ...2026-05-16-Jakarta EE 11 in Open Liberty.adoc | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/posts/2026-05-16-Jakarta EE 11 in Open Liberty.adoc b/posts/2026-05-16-Jakarta EE 11 in Open Liberty.adoc index 18bc0d671..3f887b819 100644 --- a/posts/2026-05-16-Jakarta EE 11 in Open Liberty.adoc +++ b/posts/2026-05-16-Jakarta EE 11 in Open Liberty.adoc @@ -24,7 +24,7 @@ link:https://jakarta.ee/release/11/[Jakarta EE 11] marks a significant milestone == What's New in Jakarta EE 11? -Jakarta EE 11 represents a major step forward for cloud-native enterprise Java applications. This release includes modernizing and restructuring the Test Compatibility Kits (TCKs), the new Jakarta Data specification, major updates to existing specifications, and support for the latest Java LTS release, which enables developers to leverage enhancements in Java 21, including Virtual Threads, Records. Full lists are: +Jakarta EE 11 represents a major step forward for cloud-native enterprise Java applications. This release includes modernizing and restructuring the Test Compatibility Kits (TCKs), the new Jakarta Data specification, major updates to existing specifications, and support for the latest Java LTS releases, which enables developers to leverage enhancements in Java 21, including Virtual Threads and Records. The new and updated specifications are: * <> * <> @@ -462,7 +462,7 @@ If your application uses `@ManagedBean`, you must migrate to CDI managed beans. **Migration Options:** -1. **Use CDI `@Named` for JSF/EL access** - If the bean needs to be accessible from JSF pages or Expression Language +1. **Use CDI `@Named` for Faces or Expression Language access** - If the bean needs to be accessible from JSF pages or Expression Language 2. **Use CDI scope annotations without `@Named`** - If the bean is only used for dependency injection [#interceptors-2.2] @@ -722,6 +722,7 @@ Jakarta Expression Language defines an expression language for Java applications - *Java Optional support*: Added support, disabled by default, for `java.lang.Optional` instances via the new `OptionalELResolver` *Removals:* + - All code deprecated as of Expression Language 5.0 has been removed, specifically the `getFeatureDescriptors()` method from the `ELResolver` interface - All references to the Java SecurityManager and associated APIs have been removed @@ -894,6 +895,7 @@ Jakarta Faces defines an MVC framework for building user interfaces for web appl - *FacesMessage improvements*: Implement `equals()`, `hashcode()`, `toString()` methods *Removals:* + - Deprecate unused `composite.extension` - Remove references to the SecurityManager @@ -1119,6 +1121,7 @@ Jakarta Security 4.0 provides an In-memory Identity Store, which is a developer- - *New SecurityContext method*: A new method `getAllDeclaredCallerRoles()` is added to the `SecurityContext` interface, which returns a list of all static (declared) application roles that the authenticated caller is in. *Removals and Breaking Changes:* + - All references to the `SecurityManager` have been removed from the specification - Built-in authentication mechanisms now have a qualifier by default, whereas before they were unqualified @@ -1233,7 +1236,7 @@ IMPORTANT: A developer must provide a custom implementation of the `HttpAuthenti ===== Qualifiers -HAMs - whether defined through annotations or as custom defined - can also include an optional class-level qualifier to simplify HAM injection into a custom HAM handler. For example, if you want to define qualified HAMs, you would first declare qualifier interfaces such as: +HAMs, whether defined through annotations or as custom defined - can also include an optional class-level qualifier to simplify HAM injection into a custom HAM handler. For example, if you want to define qualified HAMs, you would first declare qualifier interfaces such as: [source,java] ---- @@ -1586,6 +1589,7 @@ Jakarta RESTful Web Services provides a foundational API to develop web services - *Added JSON Merge Patch support*: Support for RFC 7396 JSON Merge Patch *Removals:* + - *Remove JAXB dependency*: Jakarta REST no longer depends on JAXB - *Remove ManagedBean support*: ManagedBean support has been removed from Jakarta REST @@ -2930,7 +2934,8 @@ This Open Liberty release provides a great ecosystem of enabling Jakarta EE 11 w ---- == Spring Boot 4.0 -Open Liberty also enables Spring Boot 4.0 applications, allowing you to leverage the latest features and improvements in the Jakarta EE 11 release. You can enable Spring Boot 4.0 feature in your `server.xml`: + +Open Liberty also enables Spring Boot 4.0 applications, allowing you to leverage the latest features and improvements in the Jakarta EE 11 release. You can enable the Spring Boot 4.0 feature in your `server.xml`: [source,xml] ---- @@ -2948,6 +2953,7 @@ Key areas to review: - *Jakarta Data 1.0*: Consider migrating existing DAO/repository code to Jakarta Data for improved productivity and reduced boilerplate - *Annotations 3.0*: Migrate from `@ManagedBean` to CDI beans - *Authentication 3.1*: Remove any SecurityManager dependencies from authentication modules +- *Authorization-3.0*: Switch to use the new `PolicyFactory` and `Policy` interfaces for your authorization modules - *CDI 4.1*: Review dependency injection configurations, especially if using custom interceptors or producers - *Servlet 6.1*: Remove any SecurityManager dependencies; test redirect handling and error dispatches - *Persistence 3.2*: Leverage Java records for embeddable types; update queries to use new operators; test java.time types @@ -2967,7 +2973,7 @@ If you are using Spring Boot, Open Liberty 26.0.0.5 and later release also enabl == Acknowledgements -Many thanks to the Jakarta EE for their hard work in bringing Jakarta EE 11 to life and Open Liberty teams ensuring it runs smoothly on Open Liberty. The contributions from the community, including feedback, testing, and code contributions, have been invaluable in making this release a success. I also want to thank my colleagues in the Open Liberty team who provided valuable feedback to this blog: Andrew Rouse, Anija K A, David Webster, Jared Anderson, Nathan Rauh, Paul Nicolucci, Mark Swatosh, Kyle Aure, Jim Kurger, Neena P Jacob, Volodymyre Siedlecki, Phu Dinh and many others. +Many thanks to the Jakarta EE community for their hard work in bringing Jakarta EE 11 to life and to the Open Liberty teams for ensuring it runs smoothly on Open Liberty. The contributions from the community, including feedback, testing, and code contributions, have been invaluable in making this release a success. I also want to thank my colleagues in the Open Liberty team who provided valuable feedback to this blog: Andrew Rouse, Anija K A, David Webster, Jared Anderson, Nathan Rauh, Paul Nicolucci, Mark Swatosh, Kyle Aure, Jim Krueger, Neena P Jacob, Volodymyr Siedlecki, Phu Dinh and many others. == Learn More From 8edf7423b8efe1f5db8e4489d8b1b46b2edd48bd Mon Sep 17 00:00:00 2001 From: emijiang Date: Wed, 20 May 2026 23:24:03 +0100 Subject: [PATCH 23/26] Address comments from Jared --- ...026-05-16-Jakarta EE 11 in Open Liberty.adoc | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/posts/2026-05-16-Jakarta EE 11 in Open Liberty.adoc b/posts/2026-05-16-Jakarta EE 11 in Open Liberty.adoc index 3f887b819..41ce354c1 100644 --- a/posts/2026-05-16-Jakarta EE 11 in Open Liberty.adoc +++ b/posts/2026-05-16-Jakarta EE 11 in Open Liberty.adoc @@ -462,7 +462,7 @@ If your application uses `@ManagedBean`, you must migrate to CDI managed beans. **Migration Options:** -1. **Use CDI `@Named` for Faces or Expression Language access** - If the bean needs to be accessible from JSF pages or Expression Language +1. **Use CDI `@Named` for Faces or Expression Language access** - If the bean needs to be accessible from Faces pages or Expression Language 2. **Use CDI scope annotations without `@Named`** - If the bean is only used for dependency injection [#interceptors-2.2] @@ -1133,8 +1133,6 @@ The Jakarta Security Specification 4.0 provides details on how to specify creden [source,java] ---- -. . . - @InMemoryIdentityStoreDefinition ( priority = 10, priorityExpression = "${80/20}", @@ -1236,7 +1234,7 @@ IMPORTANT: A developer must provide a custom implementation of the `HttpAuthenti ===== Qualifiers -HAMs, whether defined through annotations or as custom defined - can also include an optional class-level qualifier to simplify HAM injection into a custom HAM handler. For example, if you want to define qualified HAMs, you would first declare qualifier interfaces such as: +HAMs, whether defined through annotations or as custom defined, can also include an optional class-level qualifier to simplify HAM injection into a custom HAM handler. For example, if you want to define qualified HAMs, you would first declare qualifier interfaces such as: [source,java] ---- @@ -2489,23 +2487,23 @@ Jakarta Pages defines a template engine for web applications. This release remov *New features and enhancements in Pages 4.0:* -- *Enhanced ErrorData*: Updated [`ErrorData`](https://jakarta.ee/specifications/pages/4.0/apidocs/jakarta.servlet.jsp/jakarta/servlet/jsp/errordata) to add support for new servlet error attributes: +- *Enhanced ErrorData*: Updated link:https://jakarta.ee/specifications/pages/4.0/apidocs/jakarta.servlet.jsp/jakarta/servlet/jsp/errordata[`ErrorData`] to add support for new servlet error attributes: - `jakarta.servlet.error.query_string` - Access the query string from error pages - `jakarta.servlet.error.method` - Access the HTTP method from error pages -- *Java 17+ requirement*: Requires Java SE 17 or later as the minimum version +- *Java 17+ requirement*: Requires Java SE 17 as the minimum version *Removals and deprecations:* All code deprecated as of Jakarta Server Pages 3.1 has been removed: -- *ELResolver methods*: Removed methods that override [`ELResolver.getFeatureDescriptors()`](https://jakarta.ee/specifications/expression-language/6.0/apidocs/jakarta.el/jakarta/el/elresolver#getFeatureDescriptors()) as that method is removed in EL 6.0 +- *ELResolver methods*: Removed methods that override link:https://jakarta.ee/specifications/expression-language/6.0/apidocs/jakarta.el/jakarta/el/elresolver#getFeatureDescriptors()[`ELResolver.getFeatureDescriptors()`] as that method is removed in EL 6.0 - *isThreadSafe directive*: Removed the `isThreadSafe` page directive attribute as the related Servlet API interface `SingleThreadModel` has been removed in Servlet 6.0 - *jsp:plugin action*: Removed the `jsp:plugin` action and related actions as the associated HTML elements are no longer supported by any major browser -- *JspException.getRootCause()*: Removed deprecated [`JspException.getRootCause()`](https://jakarta.ee/specifications/pages/3.1/apidocs/jakarta.servlet.jsp/jakarta/servlet/jsp/jspexception#getRootCause()) method +- *JspException.getRootCause()*: Removed deprecated link:https://jakarta.ee/specifications/pages/3.1/apidocs/jakarta.servlet.jsp/jakarta/servlet/jsp/jspexception#getRootCause()[`JspException.getRootCause()`]method ==== Enhanced Error Handling with ErrorData -Jakarta Pages 4.0 adds new error attributes to [`ErrorData`](https://jakarta.ee/specifications/pages/4.0/apidocs/jakarta.servlet.jsp/jakarta/servlet/jsp/errordata) for better error page diagnostics: +Jakarta Pages 4.0 adds new error attributes to link:https://jakarta.ee/specifications/pages/4.0/apidocs/jakarta.servlet.jsp/jakarta/servlet/jsp/errordata[`ErrorData`] for better error page diagnostics: [source,jsp] ---- @@ -2552,6 +2550,7 @@ Jakarta WebSocket defines an API for Server and Client Endpoints for the WebSock - *Clarified `maxMessageSize` behavior*: Clarified the behavior when `@OnMessage.maxMessageSize` is set to a value larger than `Integer.MAX_VALUE` *Removals:* + - All references to the SecurityManager have been removed ==== Using the new getSession() method in SendResult From 9ad6253ba15db4426eaa4a9b78536629e191e06e Mon Sep 17 00:00:00 2001 From: emijiang Date: Wed, 20 May 2026 23:25:50 +0100 Subject: [PATCH 24/26] tidy up --- posts/2026-05-16-Jakarta EE 11 in Open Liberty.adoc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/posts/2026-05-16-Jakarta EE 11 in Open Liberty.adoc b/posts/2026-05-16-Jakarta EE 11 in Open Liberty.adoc index 41ce354c1..1dffbfa0b 100644 --- a/posts/2026-05-16-Jakarta EE 11 in Open Liberty.adoc +++ b/posts/2026-05-16-Jakarta EE 11 in Open Liberty.adoc @@ -2952,7 +2952,7 @@ Key areas to review: - *Jakarta Data 1.0*: Consider migrating existing DAO/repository code to Jakarta Data for improved productivity and reduced boilerplate - *Annotations 3.0*: Migrate from `@ManagedBean` to CDI beans - *Authentication 3.1*: Remove any SecurityManager dependencies from authentication modules -- *Authorization-3.0*: Switch to use the new `PolicyFactory` and `Policy` interfaces for your authorization modules +- *Authorization 3.0*: Switch to use the new `PolicyFactory` and `Policy` interfaces for your authorization modules - *CDI 4.1*: Review dependency injection configurations, especially if using custom interceptors or producers - *Servlet 6.1*: Remove any SecurityManager dependencies; test redirect handling and error dispatches - *Persistence 3.2*: Leverage Java records for embeddable types; update queries to use new operators; test java.time types From 3999d8c221c3deb8b7a52515fc2fd48f3cce2d55 Mon Sep 17 00:00:00 2001 From: emijiang Date: Wed, 20 May 2026 23:35:19 +0100 Subject: [PATCH 25/26] tidy up --- posts/2026-05-16-Jakarta EE 11 in Open Liberty.adoc | 1 - 1 file changed, 1 deletion(-) diff --git a/posts/2026-05-16-Jakarta EE 11 in Open Liberty.adoc b/posts/2026-05-16-Jakarta EE 11 in Open Liberty.adoc index 1dffbfa0b..66849f6a5 100644 --- a/posts/2026-05-16-Jakarta EE 11 in Open Liberty.adoc +++ b/posts/2026-05-16-Jakarta EE 11 in Open Liberty.adoc @@ -2490,7 +2490,6 @@ Jakarta Pages defines a template engine for web applications. This release remov - *Enhanced ErrorData*: Updated link:https://jakarta.ee/specifications/pages/4.0/apidocs/jakarta.servlet.jsp/jakarta/servlet/jsp/errordata[`ErrorData`] to add support for new servlet error attributes: - `jakarta.servlet.error.query_string` - Access the query string from error pages - `jakarta.servlet.error.method` - Access the HTTP method from error pages -- *Java 17+ requirement*: Requires Java SE 17 as the minimum version *Removals and deprecations:* From 7ddfa2216beb8c30348ab7692ef6635a094cab0f Mon Sep 17 00:00:00 2001 From: emijiang Date: Thu, 21 May 2026 09:49:51 +0100 Subject: [PATCH 26/26] add one more clarification for Jakarta security: --- .../2026-05-16-Jakarta EE 11 in Open Liberty.adoc | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/posts/2026-05-16-Jakarta EE 11 in Open Liberty.adoc b/posts/2026-05-16-Jakarta EE 11 in Open Liberty.adoc index 66849f6a5..e4202f0b8 100644 --- a/posts/2026-05-16-Jakarta EE 11 in Open Liberty.adoc +++ b/posts/2026-05-16-Jakarta EE 11 in Open Liberty.adoc @@ -1171,6 +1171,20 @@ Re-enter text: {xor}PTA9Lyg ---- +IMPORTANT: Since this feature is designed for testing and debugging purpose, when an application defines an in‑memory identity store in code, its use must also be explicitly enabled in the server.xml configuration, as shown below. By default, the allowInMemoryIdentityStores attribute is set to false, which instructs the Liberty authentication workflows not to use in‑memory identity stores, even when a custom identity store handler is present. + +[source,xml] +---- + + ... + + appSecurity-4.0 + + + ... + +---- + ==== Multiple HTTP Authentication Mechanisms The Jakarta Security 4.0 specification allows multiple HTTP Authentication Mechanisms (HAMs) to be defined within a single application: