back to all blogsSee all blog posts

Jakarta EE 11 Platform, Java 26, and more in 26.0.0.4-beta

image of author
Navaneeth S Nair on Apr 7, 2026
Post available in languages:

This beta introduces Jakarta EE 11 Platform and Web Profile, and delivers enhancements across Jakarta Authentication 3.1, Jakarta Authorization 3.0, and Jakarta Security 4.0. In addition, it provides a preview of Jakarta Data 1.1 M2, support for Java 26, MCP Server updates, and Jandex index improvements.

The Open Liberty 26.0.0.4-beta includes the following beta features (along with all GA features):

Jakarta EE 11 Platform and Web Profile

Open Liberty 24.0.0.11-beta was one of the ratifying implementations for Jakarta EE 11 Core Profile. Building on that foundation, the 26.0.0.4-beta release completes Jakarta EE 11 beta support, including Jakarta EE Application Client 11.0, Jakarta EE Web Profile 11.0, and Jakarta EE Platform 11.0.

Liberty provides convenience features that bundle all component specifications in the Jakarta EE Web Profile and Jakarta EE Platform. The webProfile-11.0 Liberty feature includes all Jakarta EE Web Profile functions, including the new Jakarta Data 1.0 component. The jakartaee-11.0 Liberty feature provides the Jakarta EE Platform version 11 implementation. For Jakarta EE 11 features in the application client, use the jakartaeeClient-11.0 Liberty feature.

These convenience features enable developers to rapidly develop applications using all APIs in the Web Profile and Platform specifications. Unlike the jakartaee-10.0 Liberty feature, the jakartaee-11.0 Liberty feature does not enable the Managed Beans specification function any longer, as this specification was removed from the platform which includes removal of the jakarta.annotation.ManagedBean annotation API. Applications relying on the @ManagedBean annotation must migrate to use CDI annotations.

The Jakarta EE 11 Platform specification removes all optional specifications from the platform, meaning Jakarta SOAP with Attachments, XML Binding, XML Web Services, and Enterprise Beans 2.x APIs functions are not included with the jakartaee-11.0 Liberty feature. To use these capabilities, explicitly add Liberty features to your server.xml feature list: xmlBinding-4.0 for XML Binding, xmlWS-4.0 for SOAP with Attachments and XML Web Services, and enterpriseBeansHome-4.0 for Jakarta Enterprise Beans 2.x APIs. Alternatively, use the equivalent versionless features with the jakartaee-11.0 platform.

When using the Liberty application client with the jakartaeeClient-11.0 feature, Jakarta SOAP with Attachments, XML Binding, and XML Web Services client functions are not available. To continue using these functions in your Liberty application client, enable the xmlBinding-4.0 and the new xmlWSClient-4.0 features in your client.xml file.

To enable the Jakarta EE 11 beta features in your Liberty server’s server.xml:

  <featureManager>
    <feature>jakartaee-11.0</feature>
  </featureManager>

Or you can add the Web Profile convenience feature to enable all of the Jakarta EE 11 Web Profile beta features at once:

  <featureManager>
    <feature>webProfile-11.0</feature>
  </featureManager>

You can enable the Jakarta EE 11 features on the Application Client Container in the client.xml:

 <featureManager>
       <feature>jakartaeeClient-11.0</feature>
 </featureManager>

Application Authentication 3.1 (Jakarta Authentication 3.1)

Jakarta Authentication defines a general SPI for authentication mechanisms, which are controllers that interact with a caller and the container’s environment to obtain and validate the caller’s credentials. It then passes an authenticated identity (such as name and groups) to the container.

The 3.1 version of the Jakarta Authentication API 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 also. These changes are part of Jakarta EE 11’s removal of SecurityManager support.

You can enable the Jakarta Authentication 3.1 feature by adding the appAuthentication-3.1 Liberty feature in the server.xml file:

    <featureManager>
        <feature>appAuthentication-3.1</feature>
    </featureManager>

For more information, see the Jakarta Authentication 3.1 specification and javadoc.

Application Authorization 3.0 (Jakarta Authorization 3.0)

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. It also defines algorithms that transform security constraints for specific containers, such as Jakarta Servlet or Jakarta Enterprise Beans, into these permissions.

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, now you can 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:

<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="https://jakarta.ee/xml/ns/jakartaee"
  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xsi:schemaLocation="https://jakarta.ee/xml/ns/jakartaee https://jakarta.ee/xml/ns/jakartaee/web-app_6_1.xsd"
  version="6.1">

  <context-param>
    <param-name>jakarta.security.jacc.PolicyConfigurationFactory.provider</param-name>
    <param-value>com.example.MyPolicyConfigurationFactory</param-value>
  </context-param>

  <context-param>
    <param-name>jakarta.security.jacc.PolicyFactory.provider</param-name>
    <param-value>com.example.MyPolicyFactory</param-value>
  </context-param>

</web-app>

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:

    public boolean impliesByRole(Permission p, Subject subject) {
        Map<String, PermissionCollection> 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<String> mappedRoles = principalMapper.getMappedRoles(subject);
        for (String mappedRole : mappedRoles) {
            PermissionCollection rolePermissions = perRolePermissions.get(mappedRole);
            if (rolePermissions != null && rolePermissions.implies(p)) {
                return true;
            }
        }
        return false;
    }

You can enable the Jakarta Authorization 3.0 feature by adding the appAuthorization-3.0 Liberty feature in the server.xml file:

    <featureManager>
        <feature>appAuthorization-3.0</feature>
    </featureManager>

For more information, see the Jakarta Authorization 3.0 specification and javadoc.

Application Security 6.0 (Jakarta Security 4.0)

An In-memory Identity Store 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.

  • 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.

  • 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.

  • References to the IdentityStorePermission class are removed as it was previously deprecated.

All features are available as part of appSecurity-6.0.

All feature use cases are targeted towards Jakarta EE web application developers who want to move to a modern Jakarta version and use implementations of the Jakarta Security 4.0 specification.

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.

Developers can also implement custom identity stores, but doing so requires bespoke code to collect, validate, and manage credential information. This effort can divert attention from core application development. A built-in, in-memory identity store reduces this burden for non-production use cases such as testing, debugging, and demonstrations.

Multiple HAMs

It is now possible for multiple authentication mechanisms to logically act as a single HTTP Authentication Mechanism (HAM), providing a more flexible, dynamic, and configurable approach to the authentication workflow.

Access to the HttpAuthenticationMechanism is now abstracted by an internal implementation of the new Jakarta HttpAuthenticationMechanismHandler interface. This implementation prioritizes custom‑defined HAMs first and then falls back to the built‑in (annotation‑defined) HAMs, in the following order: OpenID, custom form, form, and basic authentication mechanisms.

Developers are free to provide their own implementation of the HttpAuthenticationMechanismHandler, allowing them to define a custom prioritization strategy for selecting HAMs. They must supply such an implementation if any built‑in HAMs are defined with the optional qualifiers attribute set.

getAllDeclaredCallerRoles()

The SecurityContext interface implementation is updated to include a new method, getAllDeclaredCallerRoles(), which returns a list of the declared application roles for an authenticated caller. If the caller is not authenticated or has no declared roles, the method returns an empty list.

How to use

Enable Jakarta Security 4.0 features within the server.xml file by adding the appSecurity-6.0 feature as shown in the following example:

<server description = "Liberty server">

    . . .

    <featureManager>
        <feature>appSecurity-6.0</feature>
    </featureManager>

    <webAppSecurity allowInMemoryIdentityStores="true"/>

    . . .
As shown in the preceding example, when an application defines an in‑memory identity store in code, its use must also be explicitly enabled in the server.xml configuration. 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. For applications that rely on multiple HAMs, the allowInMemoryIdentityStores attribute does not need to be set.

In-Memory Identity Store

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, as shown in the following example:

. . .

@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 that uses an Open Liberty–supported encoding mechanism, as illustrated in the next example.

@InMemoryIdentityStoreDefinition (
    value = {
        @Credentials(callerName = "jasmine",  password = "{xor}LDo8LTorbg==", groups = { "caller", "user" } ),
        @Credentials(callerName = "frank", groups = { "user" }, password = "{hash}ARAAA <sequence shortened> Fyyw=="),
        @Credentials(callerName = "sally", groups = { "user" }, password = "{aes}ARAFIYJ <sequence shortened> 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.

wlp/bin/securityUtility encode --encoding=xor
Enter text: <enter text to encode>
Re-enter text:
{xor}PTA9Lyg

Multiple HAMs

Application Specification

The Jakarta Security 4.0 specification allows multiple multiple HTTP Authentication Mechanisms (HAMs) to be defined within a single application, as shown in the following example:

@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, as shown in the following example:

@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).

@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.

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. Additional information about creating and using a custom HAM handler is provided in a later section.

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:

@Qualifier
@Retention(RUNTIME)
@Target({TYPE, METHOD, FIELD, PARAMETER})
public @interface Admin {
}

(Not shown is the qualifier definitions for User, Fallback which follow an identical pattern, and are used below).

Then, import and use the qualifiers in your in-built or custom HAM specifications, such as:

@CustomFormAuthenticationMechanismDefinition(<existing details not shown>, qualifiers={Admin.class})

@BasicAuthenticationMechanismDefinition(<existing details not shown>, qualifiers={User.class})

and

@ApplicationScoped
@Fallback   // add custom qualifier to be used during injection
public class CustomHAM implements HttpAuthenticationMechanism {

    @Override
    public AuthenticationStatus validateRequest (. . .)  {
         . . .
    }
}

The three HAMs defined in the example are available for injection into a custom HAM handler based on their qualifier names. This scenario represents the typical use case for applications that define multiple HAMs.

If qualifiers are specified in any of the built-in HAM definitions, a custom HAM handler must be provided; otherwise, an error is raised. This requirement comes directly from the Jakarta Security specification. Details about creating and using a custom HAM handler are explained in the next section.

To implement a custom HAM handler, define a public class that is annotated with @ApplicationScoped that implements the HttpAuthenticationMechanismHandler interface. Also, inject the qualified HAMs by using standard CDI syntax, as shown in the following section:

@Default
@ApplicationScoped
public class CustomHAMHandler implements HttpAuthenticationMechanismHandler {

    @Inject @Admin // this will be the FormHAM
    private HttpAuthenticationMechanism adminHAM;

    @Inject @User // this will be the BasicHAM
    private HttpAuthenticationMechanism userHAM;

    @Inject @Fallback // this will be the Custom HAM
    private HttpAuthenticationMechanism fallbackHAM;

    public AuthenticationStatus validateRequest(HttpServletRequest request,                                                HttpServletResponse response,
     HttpMessageContext context) throws AuthenticationException {

        String path = request.getRequestURI();

        // route to appropriate mechanism based on path, default to my-realm
        if (path.startsWith("/admin")) { // FormHAM
            return adminHAM.validateRequest(request, response, context);
        } else if (path.startsWith("/user")) { // BasicHAM
            return userHAM.validateRequest(request, response, context);
        } else { // Custom HAM
            return fallbackHAM.validateRequest(request, response, context);
    }
}

This custom HAM handler takes priority over the internal HAM handler, allowing a different prioritisation algorithm to be implemented.

getAllDeclaredCallerRoles()

To use the new SecurityContext method, inject the SecurityContext implementation into your application and call the method directly, as shown in the following section:

    @Inject
    private SecurityContext securityContext;

. . .

    Set<String> allDeclaredCallerRoles = securityContext.getAllDeclaredCallerRoles();

    System.out.println("All declared caller roles for caller ["
        + securityContext.getCallerPrincipal().getName()
        + "] are "
        + allDeclaredCallerRoles.toString());

Learn more

Further information can be found in the Jakarta Security Specification 4.0:

For more information about the securityUtility command, see the WAS Liberty base topic.

Beta support for Java 26

Java 26 is a recent Java release that introduces new features and enhancements over earlier versions that can be useful to review. This release is not a long-term support (LTS) release.

There are 10 new features (JEPs) in Java 26. Five are test features and five are fully delivered.

Test Features:

Delivered Features:

A new change JEP 500 ("Prepare to Make Final Mean Final") in Java 26 starts enforcing true immutability of final fields by restricting their mutation when using deep reflection. In Java 26, such mutations still work but trigger runtime warnings by default, preparing developers for stricter enforcement. Future releases would likely throw exceptions instead, making the final truly nonmutable.

Developers can opt in early to this stricter behavior by using a JVM flag (for example, --illegal-final-field-mutation=deny) to detect issues sooner. This change improves program correctness, security, and JVM optimizations.

Take advantage of these changes now to gain more time to evaluate how your applications and microservices behave on Java 26.

Get started today by downloading the latest release of IBM Semeru Runtime 26 or Temurin 26, then download and install the Open Liberty 26.0.0.4-beta. Update your Liberty server’s server.env file with JAVA_HOME set to your Java 26 installation directory and start testing.

For more information on Java 26, see the Java 26 release notes page and API Javadoc page.

Updates to mcpServer-1.0

The Model Context Protocol (MCP) is an open standard that enables AI applications to access real-time information from external sources. The Liberty MCP Server feature mcpServer-1.0 allows developers to expose the business logic of their applications, allowing it to be integrated into agentic AI workflows.

This beta release of Open Liberty includes updates to the mcpServer-1.0 feature including dynamic registration of tools and support for version 2025-11-25 of the protocol.

Dynamically register MCP tools

Tools can now be registered dynamically through an API. This capability allows the set of available tools on the server to be adjusted based on configuration or environment.

Tools can be registered by injecting ToolManager and calling its methods to add, remove, and list the available tools on the server. The full Javadoc for ToolManager can be found within the liberty beta in dev/api/ibm/javadoc/io.openliberty.mcp_1.0-javadoc.zip.

Tools can be registered when the application starts through the CDI Startup event. See the following example where the Startup event is used to register a weather forecast tool only if a WeatherClient bean is available.

    @Inject
    ToolManager toolManager;

    @Inject
    Instance<WeatherClient> weatherClientInstance;

    private void createWeatherTool(@Observes Startup startup) {
        if (weatherClientInstance.isResolvable()) {
            WeatherClient weatherClient = weatherClientInstance.get();
            toolManager.newTool("getForecast")
                       .setTitle("Weather Forecast Provider")
                       .setDescription("Get weather forecast for a location")
                       .addArgument("latitude", "Latitude of the location", true, Double.class)
                       .addArgument("longitude", "Longitude of the location", true, Double.class)
                       .setHandler(toolArguments -> {
                           Double latitude = (Double) toolArguments.args().get("latitude");
                           Double longitude = (Double) toolArguments.args().get("longitude");
                           String result = weatherClient.getForecast(
                                     latitude,
                                     longitude,
                                     4,
                                     "temperature_2m,snowfall,rain,precipitation,precipitation_probability");
                           return ToolResponse.success(result);
                       })
                       .register();
        }
    }

Tool registration starts with newTool(), then the information about the tool is added, including its title, description, and arguments. The handler supplies the code to run when the tool is called. It has one parameter, which is a ToolArguments object, which provides access to the arguments and other information about the tool call request. The handler must always create and return a ToolResponse.

Accept the 2025-11-25 MCP protocol

The mcpServer-1.0 feature now accepts version 2025-11-25 of the Model Context Protocol (MCP), although it does not support any new features introduced in this version of the protocol.

Supporting MCP 2025-11-25 introduces two notable behavior changes:

  • Tool name restrictions: Tool names are now limited to ASCII letters ('A–Z, a–z'), digits ('0–9'), and the underscore ('_'), hyphen ('-'), and dot ('.') characters.

  • Improved handling of invalid tool arguments: Invalid arguments passed when invoking a tool are now treated as tool execution errors rather than protocol errors. This change allows the LLM to receive feedback that the tool was called incorrectly and to retry with corrected arguments.

Paginated results

The result of a tools/list call is now paginated with a page size of 20. This means that if there are more than 20 tools deployed on the server, clients make several small calls to retrieve the list of tools, rather than one large call.

Bug fixes

  • During cancellation of a tool call, we check that both the session id and the authenticated user match the session id and the user that made the tool call. Previously only the session id was checked.

  • Messages that are returned to the MCP client no longer contain OpenLiberty message codes.

  • Structured content is only returned when client is using protocol version 2025-06-18 or later.

Further information

Preview of some Jakarta Data 1.1 M2 capability

Previews some new capability at the Jakarta Data 1.1 Milestone 2 level: Constraint subtype parameters for repository methods that constraints to repository @Find operations and limited use of Restriction with repository @Find operations. Also included from the prior beta are: retrieving a subset/projection of entity attributes and the @Is annotation.

Previously, parameter-based @Find reposotory methods could filter results only using equality conditions. This limitation has now been removed, allowing additional filtering options to be defined.

Filtering can now be specified in several ways. One approach is to define the repository method parameter as a Constraint subtype, or to indicate the constraint subtype by using the @Is annotation. Alternatively, a repository @Find method can include a special parameter of type Restriction, allowing the application to supply one or more restrictions dynamically at runtime when the method is invoked.

In Jakarta Data, you define simple Java objects called entities to represent data, and interfaces called repositories to define data operations. You inject a repository into your application and use it. The implementation of the repository is automatically provided.

Start by defining an entity class that corresponds to your data. With relational databases, the entity class corresponds to a database table and the entity properties (public methods and fields of the entity class) generally correspond to the columns of the table. An entity class can be:

  • annotated with jakarta.persistence.Entity and related annotations from Jakarta Persistence

  • a Java class without entity annotations, in which case the primary key is inferred from an entity property named id or ending with Id and an entity property named version designates an automatically incremented version column.

Here’s a simple entity:

@Entity
public class Product {
    @Id
    public long id;

    public String name;

    public double price;

    public double weight;
}

After you define the entity to represent the data, it is usually helpful to have your IDE generate a static metamodel class for it. By convention, static metamodel classes begin with the underscore ('_') character, followed by the entity name.

Because this beta is available well before the release of Jakarta Data 1.1, IDE support for this generation cannot be expected yet. However, a static metamodel class can be provided in the same form that an IDE would generate for the Product entity.

@StaticMetamodel(Product.class)
public interface _Product {
    String ID = "id";
    String NAME = "name";
    String PRICE = "price";
    String WEIGHT = "weight";

    NumericAttribute<Product, Long> id = NumericAttribute.of(
            Product.class, ID, long.class);
    TextAttribute<Product> name = TextAttribute.of(
            Product.class, NAME);
    NumericAttribute<Product, Double> price = NumericAttribute.of(
            Product.class, PRICE, double.class);
    NumericAttribute<Product, Double> weight = NumericAttribute.of(
            Product.class, WEIGHT, double.class);
}

The first half of the static metamodel class includes constants for each of the entity attribute names so that you don’t need to otherwise hardcode string values into your application. The second half of the static metamodel class provides a special instance for each entity attribute, from which you can build restrictions and sorting to apply to queries at run time. This capability enables many powerful operations with repository queries, but those features are deferred to a future beta.

A repository defines operations that are related to the Product entity. Your repository interface can inherit from built-in interfaces such as BasicRepository and CrudRepository to gain various general-purpose repository methods for inserting, updating, deleting, and querying for entities. In addition to these capabilities, the repository can define custom operations by using the static metamodel and annotations such as @Find or @Delete.

@Repository(dataStore = "java:app/jdbc/my-example-data")
public interface Products extends CrudRepository<Product, Long> {

    // Filtering is pre-defined at development time by the @Is annotation,
    @Find
    @OrderBy(_Product.PRICE)
    @OrderBy(_Product.NAME)
    List<Product> costingUpTo(@By(_Product.PRICE) @Is(AtMost.class) double maxPrice);

    // Constraint types, such as Like, can also pre-define filtering.
    // Allow additional filtering at run time by including a Restriction,
    @Find
    Page<Product> named(@By(_Product.Name) Like namePattern,
                        Restriction<Product> filter,
                        Order<Product> sorting,
                        PageRequest pageRequest);

    // Retrieve a single entity attribute, identified by @Select,
    @Find
    @Select(_Product.PRICE)
    Optional<Double> priceOf(@By(_Product.ID) long productNum);

    // Retrieve multiple entity attributes as a Java record,
    @Find
    @Select(_Product.WEIGHT)
    @Select(_Product.NAME)
    Optional<WeightInfo> weightAndNameOf(@By(_Product.ID) long productNum);

    static record WeightInfo(double itemWeight, String itemName) {}
}

Here is an example of using the repository and static metamodel:

@DataSourceDefinition(name = "java:app/jdbc/my-example-data",
                      className = "org.postgresql.xa.PGXADataSource",
                      databaseName = "ExampleDB",
                      serverName = "localhost",
                      portNumber = 5432,
                      user = "${example.database.user}",
                      password = "${example.database.password}")
public class MyServlet extends HttpServlet {
    @Inject
    Products products;

    protected void doGet(HttpServletRequest req, HttpServletResponse resp)
            throws ServletException, IOException {
        // Insert:
        Product prod = ...
        prod = products.insert(prod);

        // Filter by supplying values only:
        List<Product> found = products.costingUpTo(50.0, "%keyboard%");

        // Compute filtering at run time,
        Page<Product> page1 = products.named(
                        Like.suffix("printer"),
                        Restrict.all(_Product.price.times(taxRate).plus(shipping).lessThan(250.0),
                                     _Product.weight.times(kgToPounds).between(10.0, 20.0)),
                        Order.by(_Product.price.desc(),
                                 _Product.name.asc(),
                                 _Product.id.asc()),
                        PageRequest.ofSize(10));

        // Find one entity attribute:
        price = products.priceOf(prod.id).orElseThrow();

        // Find multiple entity attributes as a Java record:
        WeightInfo info = products.getWeightAndName(prod.id);
        System.out.println(info.itemName() + " weighs " + info.itemWeight() + " kg.");

        ...
    }
}

Learn more

  • Jakarta Data 1.1 API Javadoc

  • Jakarta Data 1.1 overview page

  • Maven coordinates for Data 1.1 Milestone 2:

    <dependency>
        <groupId>jakarta.data</groupId>
        <artifactId>jakarta.data-api</artifactId>
        <version>1.1.0-M2</version>
    </dependency>
    This beta includes only the Data 1.1 features for entity subsets and projections, the @Is annotation, and Constraint subtypes as parameters for repository Find methods. It also includes limited support for Restriction as a special parameter for repository Find methods. The navigate operations are not implemented for restrictions and constraints. Other new Data 1.1 features are not included in this beta.

Support for Reading Jandex Indexes from WEB-INF/classes in Web Modules.

Previously, Jandex indexes across all module types were read only from META-INF/jandex.idx. This update extends support for web modules to also read indexes from WEB-INF/classes/META-INF/jandex.idx, bringing it in line with industry practices.

Jandex indexes are read from the original or the new location only when Jandex use is enabled. For compatibility with earlier versions, reading indexes from the new location must be enabled using a new application property.

Benefits

  • Improved Compatibility: Improve compatibility with industry standard practices.

  • Faster Startup Times: Continue to benefit from Jandex’s efficient annotation indexing.

Target Persona

Customers who wish to place Jandex indexes at industry standard locations in their web modules.

Target Features

This update applies to the scanning of classes within applications. Class scanning (which includes annotation scanning) is a general purpose function that is used by many features.

Problem Description

To gather information about application classes, including annotation data, the classes must be scanned. This scanning process is expensive because the entire application must be read and processed.

To improve performance, class scanning can be performed during application packaging, with the scan results stored in an index within the application. Jandex provides this capability.

Jandex scan results are typically written to META-INF/jandex.idx.

For JAR files, this path is relative to the root of the JAR. For WAR files, however, the META-INF/jandex.idx path can have two interpretations: it can be relative to the root of the WAR file, or relative to the WEB-INF/classes directory.

Before this update, Liberty used the location relative to the root of the WAR file. However, industry-standard practices place the location relative to the WEB-INF/classes directory. This update enables Liberty to also use the WEB-INF/classes location.

How to Use

Support for reading Jandex indexes under WEB-INF/classes is enabled by a new property on the application and applicationManager elements:

  • Property: useJandexUnderClasses

  • Description: Enables use of a Jandex index for WAR WEB-INF/classes under /WEB-INF/classes/META-INF/jandex.idx.

  • Possible values: true | false

  • Default value: false

Example: applicationManager

<applicationManager useJandex="true" useJandexUnderClasses="true"/>

Example: application

<application location="TestServlet40.ear" useJandex="true" useJandexUnderClasses="true"/>

When the new property is placed on an application manager element, it applies to all web modules of all applications. When the new property is placed on an application element, the property applies to only that application. If the property is set on both the application manager element and an application element, the value on the application element overrides the value on the application manager element for that application.

Limitation

Jandex index support requires explicit enablement. See the useJandex property  on applicationManager and on application elements. The new useJandexUnderClasses property is meaningful only if the useJandex property is true.

For compatibility with an earlier versions, reads of Jandex from the new location requires explicit enablement. See the new useJandexUnderClasses property, as documented previously. Explicit enablement is required to prevent applications from accidentally reading an out of date Jandex index from the new location. An out of date Jandex index might cause hard to detect application errors.

The name of the new property, useJandexUnderClasses, is subject to revision.

Try it now

To try out these features, update your build tools to pull the Open Liberty All Beta Features package instead of the main release. To enable the MCP server feature, follow the instructions from MCP standalone blog. The beta works with Java SE 25, Java SE 21, Java SE 17, Java SE 11, and Java SE 8.

If you’re using Maven, you can install the All Beta Features package using:

<plugin>
    <groupId>io.openliberty.tools</groupId>
    <artifactId>liberty-maven-plugin</artifactId>
    <version>3.12.0</version>
    <configuration>
        <runtimeArtifact>
          <groupId>io.openliberty.beta</groupId>
          <artifactId>openliberty-runtime</artifactId>
          <version>26.0.0.4-beta</version>
          <type>zip</type>
        </runtimeArtifact>
    </configuration>
</plugin>

You must also add dependencies to your pom.xml file for the beta version of the APIs that are associated with the beta features that you want to try. For example, the following block adds dependencies for two example beta APIs:

<dependency>
    <groupId>org.example.spec</groupId>
    <artifactId>exampleApi</artifactId>
    <version>7.0</version>
    <type>pom</type>
    <scope>provided</scope>
</dependency>
<dependency>
    <groupId>example.platform</groupId>
    <artifactId>example.example-api</artifactId>
    <version>11.0.0</version>
    <scope>provided</scope>
</dependency>

Or for Gradle:

buildscript {
    repositories {
        mavenCentral()
    }
    dependencies {
        classpath 'io.openliberty.tools:liberty-gradle-plugin:4.0.0'
    }
}
apply plugin: 'liberty'
dependencies {
    libertyRuntime group: 'io.openliberty.beta', name: 'openliberty-runtime', version: '[26.0.0.4-beta,)'
}

Or if you’re using container images:

FROM icr.io/appcafe/open-liberty:beta

Or take a look at our Downloads page.

If you’re using IntelliJ IDEA, Visual Studio Code or Eclipse IDE, you can also take advantage of our open source Liberty developer tools to enable effective development, testing, debugging and application management all from within your IDE.

For more information on using a beta release, refer to the Installing Open Liberty beta releases documentation.

We welcome your feedback

Let us know what you think on our mailing list. If you hit a problem, post a question on StackOverflow. If you hit a bug, please raise an issue.