Key Takeaways
- Jakarta EE 11 introduces a new specification, Jakarta Data, with updates to 16 specifications and a fresh, updated Technology Compatibility Kit.
- The release of Jakarta EE 11 was delayed to focus on modernizing the Technology Compatibility Kit (TCK) for improved compatibility testing and lowering the barrier for adding more tests as the Jakarta EE ecosystem grows and evolves.
- Jakarta EE 11 now requires Java 17 as a minimum version and support for Java 21, bringing support for new features such as Java records and virtual threads.
- Jakarta EE 12 will feature advancing capabilities in data management.
- The Jakarta Persistence, Jakarta Validation and Jakarta Expression Language specifications include support for Java records.
- The Jakarta Concurrency specification supports the use of Virtual Threads via a modification to a single attribute when using Java 21.
Jakarta EE 11 is now available, delivering additional features and capabilities for improving software developer productivity and enabling further innovation. So you may be asking, “What’s new in this version?” and “What comes next?” This article will address these questions.
Jakarta EE, formerly known as Java EE, stands for the Jakarta Enterprise Edition and its associated specifications. It is an open-source ecosystem containing thirty active specifications in the Jakarta EE 11 Platform. You can think of a specification as a unit or a component within the ecosystem.
For example, Jakarta Persistence is a specification for handling relational databases, while Jakarta Contexts and Dependency Injection (CDI) is a specification for injecting dependencies within your application. The Jakarta EE 11 Web Profile and Jakarta EE 11 Core Profile are subsets of the Jakarta EE 11 Platform that we will cover later in this article.
Thus, as a Java developer, you are already using the Jakarta EE platform, regardless of whether you are using Spring, Quarkus, Helidon, Open Liberty, Payara, or Tomcat. For example, when you use servlets in your application, you are using the Jakarta Servlet specification.
The Spring ecosystem uses Jakarta EE as a dependency. For example, Spring Data relies on the Jakarta Persistence specification.
The Jakarta EE Working Group decided to delay the release of Jakarta EE 11 to focus on modernizing the Technology Compatibility Kit (TCK). Benefits for having made this investment include improved compatibility testing and a lowered barrier for adding more tests as the Jakarta EE ecosystem grows and evolves.
Now that we have provided an overview of the Jakarta EE ecosystem and why it is relevant for the Java community, let’s discuss what’s new in Jakarta EE 11.
What’s New in Jakarta EE 11?
Jakarta EE 11 marks a significant step forward in simplifying enterprise Java with a focus on increasing developer productivity and performance. Key highlights include the introduction of the new Jakarta Data specification, Java 17 as a minimum requirement, support for Java 21, and updates to sixteen specifications. The TCK was also updated featuring migrations from Ant to Maven and TestHarness to Arquillian.
This release provides improved integration with Java records on several specifications, such as Jakarta Persistence, Jakarta Validation, and Jakarta Expression Language, as well as support for virtual threads in Jakarta Concurrency when using Java 21.
Figure 1 below shows the specifications under the Jakarta EE Platform, Web Profile and Core Profile. As you can see, Jakarta Data is the new specification and updates to sixteen specifications, such as Jakarta CDI 4.1 and Jakarta Persistence 3.2.
Figure 1: The Jakarta EE specifications under the Platform, Web Profile and Core Profile.
The strategy of continually increasing integration among platforms has been established. Jakarta EE 11 evolves context and dependency injection with the removal of the Jakarta Managed Beans specification in favor of CDI alternatives. Furthermore, this version also removed all references to Security Manager that was deprecated in Java 17 and permanently disabled in Java 24. This opens up space for more modern security capabilities within Jakarta EE. Moreover, this version also removed optional features, such as Jakarta SOAP with Attachments, Jakarta XML Binding, and Jakarta XML Web Services, primarily to simplify the experience for new vendors within the platform.
When Jakarta EE enables Java 17 as a minimum requirement supporting Java 21, it is natural to expect integration and capabilities with the recent, more popular new features of Java SE: Java records and virtual threads.
Let’s start with Java records. First, let’s consider this Driver
data structure:
public record Driver(String name, String document, Integer age) {
}
Now, let’s apply some validation using annotations provided by the Jakarta Validation specification. We want to ensure that the name
and document
parameters cannot be blank with a specified minimum and maximum size. Then, we can assert a minimum age to ensure that people under sixteen years old cannot drive.
public record Driver(@NotBlank @Size(min = 5, max = 50)String name,
@NotBlank @Size(min = 5, max = 50) String document,
@Min(16)Integer age) {
}
With Jakarta Persistence, you can use Java records as embedded classes and as IDs. However, there is no support in the specification for Java records and entities. We can now use the @Embeddable
annotation to organize the code structure more effectively, particularly the immutable part. For example, consider an address that generally doesn’t change, but if it does change, all the attributes of an address, that is, the city, street, and zip code will need to change. The code below shows a sample of the usage of Java records with Jakarta Persistence:
@Embeddable
public record Address(String street, String city, String zipCode) {}
@Entity
public class Customer {
@Id
@GeneratedValue
private Long id;
private String name;
@Embedded
private Address address;
}
We can also use the @Embedded
annotation as a composite key. Thus, you can use it on the newest Jakarta EE projects. For example, given an order item with a composite key that combines the order and the product, we can now use this structure as a record. This is shown in the code below.
@Embeddable
public record OrderItemId(Long orderId, Long productId) implements Serializable {}
@Entity
public class OrderItem {
@EmbeddedId
OrderItemId id;
int quantity;
@ManyToOne(fetch = FetchType.LAZY)
@MapsId("orderId")
Order order;
}
@Entity
public class Order {
@Id
@GeneratedValue
Long id;
String customerName;
@OneToMany(mappedBy = "order", cascade = CascadeType.ALL, orphanRemoval = true)
List<OrderItem> items;
}
In Jakarta EE 11, you can also utilize virtual threads. The Jakarta Concurrency specification provides a straightforward way to enable virtual threads in your code. This is accomplished by simply setting a boolean flag in the @ManagedExecutorDefinition
annotation. The sample code below demonstrates the creation of two CDI qualifiers: one using virtual threads and the second using the platform thread.
@Qualifier
@Retention(RUNTIME)
@Target({ FIELD, METHOD, PARAMETER, TYPE })
public @interface WithVirtual {}
@Qualifier
@Retention(RUNTIME)
@Target({ FIELD, METHOD, PARAMETER, TYPE })
public @interface WithoutVirtual {}
@ManagedExecutorDefinitions({
@ManagedExecutorDefinition(
name = "java:module/concurrent/ExecutorWithVirtual",
context = "java:module/concurrent/ContextWithVirtual",
qualifiers = WithVirtual.class,
virtual = true,
maxAsync = 10,
hungTaskThreshold = 10000
),
@ManagedExecutorDefinition(
name = "java:module/concurrent/ExecutorWithoutVirtual",
context = "java:module/concurrent/ContextWithoutVirtual",
qualifiers = WithoutVirtual.class,
virtual = false,
maxAsync = 5,
hungTaskThreshold = 20000
)
})
@ContextServiceDefinition(
name = "java:module/concurrent/ContextWithVirtual",
propagated = {SECURITY, APPLICATION}
)
@ContextServiceDefinition(
name = "java:module/concurrent/ContextWithoutVirtual",
propagated = {SECURITY, APPLICATION}
)
public class ConcurrencyConfig {
}
@ApplicationScoped
public class TaskService {
@Inject
@WithVirtual
ManagedExecutorService virtualExecutor;
@Inject
@WithoutVirtual
ManagedExecutorService platformExecutor;
public void runWithVirtual() {
virtualExecutor.execute(() ->
System.out.println("[VIRTUAL] running in: " + Thread.currentThread())
);
}
public void runWithPlatform() {
platformExecutor.execute(() ->
System.out.println("PLATFORM] running in: " + Thread.currentThread())
);
}
}
Finally, Jakarta Data, the new specification in Jakarta EE 11, provides capability at the persistence layer, making it easier to access both SQL and NoSQL databases using a single interface. This is similar to using Spring Data for those familiar with it.
With Jakarta Data, you can generate queries, data modifications, and two types of paginations: offset and cursor. Using an interface and a couple of simple instructions, the provider will do the work of converting this for you. With the built-in repository interfaces, which you use to generate an interface, Jakarta Data extends from a hierarchy interface or a custom interface that produces the actions and terminology from scratch.
For example, the code below defines a CarRepository
interface, where we extend from the built-in BasicRepository
interface. As a result, the CarRepository
provides several action methods, such as save, delete, and others.
@Repository
public interface CarRepository extends BasicRepository<Car, Long> {
List<Car> findByType(CarType type);
Optional<Car> findByName(String name);
}
@Inject
CarRepository repository;
...
Car ferrari = Car.id(10L).name("Ferrari").type(CarType.SPORT);
repository.save(ferrari);
Naturally, there are cases where you can define your terminology, going deep into Domain-Driven Design (DDD) and ubiquitous language. For example, you can create your Car
collection and define its actions:
@Repository
public interface Garage {
@Insert
Car park(Car car);
}
Jakarta Data also introduces annotations that you can use to define database operations, such as the annotations @Insert
, @Update
, @Delete
, @Find
, and @Save
. Furthermore, it brings three ways to generate queries on the repository:
- Query by method, similar to Spring Data, where you can define a conventional nomenclature to do queries, such as to find a
Product
by its type.
List<Product> findByType(ProductType type);
- The
@Find
annotation allows you to write a query that will match with the parameter. In this example, the annotation will return an instance ofStream<Car>
based on its type.
@Find
Stream<Car> type(@By("type") CarType type);
- With theJakarta Data Query Language (JDQL), a subset of Jakarta Persistence Query Language (JPQL), you can use a query that executes queries using text.
@Query("where author.name = :author order by title")
List<Book> findByAuthorSortedByTitle(String author);
Furthermore, pagination, which usually requires a significant amount of code, can be simplified using Jakarta Data because it defines the return type and includes the PageRequest
interface. The code below shows two sample uses: one with the offset pagination and the second one with the cursor-based pagination.
@Repository
public interface CarRepository extends BasicRepository<Car, Long> {
Page<Car> findByTypeOrderByName(CarType type, PageRequest pageRequest);
@Find
@OrderBy(_Car.NAME)
@OrderBy(_Car.VIN)
CursoredPage<Car> type(CarType type, PageRequest pageRequest);
}
In this overview of Jakarta EE 11, you can see improved support for Java records, virtual threads, and the new persistence layer capabilities with Jakarta Data. But what does the future hold for Jakarta EE? Is there any plan for Jakarta EE 12? Yes, and we will cover that in the next section.
Jakarta EE 12 Targets Consistency and Configuration
Even with the recent release of Jakarta EE 11, work has already started for Jakarta EE 12 with a draft plan that includes the addition of new specifications to the Web Profile. The targeted release date is July 2026.
It is essential to note that this plan is still under development, and, as with any software development project, may be delayed or modified during the development process. Figure 2 below shows the status of the specifications for Jakarta EE 12.
Figure 2: The specifications and their respective versions proposed for Jakarta EE 12.
As Gavin King, Senior Distinguished Engineer at IBM and Creator of Hibernate, said on his social media, we have an exciting moment for persistence in the next version of Jakarta EE. If Jakarta EE 12 could have a nickname, we could define it as the Data Age, where it will include polyglot persistence within the Jakarta EE ecosystem. It will enable Jakarta to “speak” both SQL and NoSQL, thanks to the inclusion of Jakarta NoSQL 1.1 in the Platform.
@Inject
Template template;
...
Car ferrari = Car.id(1L)
.name("Ferrari")
.type(CarType.SPORT);
template.insert(ferrari);
Optional<Car> car = template.find(Car.class, 1L);
template.delete(Car.class, 1L);
Furthermore, several improvements have been made to Jakarta Data, including enhancements to the metamodel, which enable dynamic capabilities. Thus, we can dynamically change parameters. For example, imagine a scenario where we have a REST resource that allows you to define parameters, including those conditions.
In this case, we have a ProductSearchFilter
class, where we can set up conditions used to return a Product
based on those conditions. As you can see, the metadata generates fluent API conditions.
public class ProductSearchFilter {
@QueryParam("type")
private String type;
@QueryParam("minPrice")
private BigDecimal minPrice;
@QueryParam("maxPrice")
private BigDecimal maxPrice;
@QueryParam("nameContains")
private String nameContains;
public Optional<String> type() {
return Optional.ofNullable(type);
}
public Optional<BigDecimal> minPrice() {
return Optional.ofNullable(minPrice);
}
public Optional<BigDecimal> maxPrice() {
return Optional.ofNullable(maxPrice);
}
public Optional<String> nameContains() {
return Optional.ofNullable(nameContains);
}
}
@GET
public List<Product> find(@BeanParam ProductSearchFilter filter) {
List<Restriction<Product>> conditions = new ArrayList<>();
filter.type().ifPresent(value ->
conditions.add(_Product.type.equalTo(ProductType.valueOf(value)))
);
filter.minPrice().ifPresent(value ->
conditions.add(_Product.price.greaterThan(value))
);
filter.maxPrice().ifPresent(value ->
conditions.add(_Product.price.lessThan(value))
);
filter.nameContains().ifPresent(value ->
conditions.add(_Product.name.contains(value))
);
return products.findAll(Restriction.all(conditions));
}
For polyglot persistence, we also have a new specification, Jakarta Query, with the goal of having a single query that defines an object-oriented query language designed for use with Jakarta Persistence, Jakarta Data, and Jakarta NoSQL.
It makes sense to consolidate all the persistence specifications. We have taken the first step with Jakarta Data, and now we will take this next step forward with the new Jakarta Query specification.
Building this specification will be challenging, primarily due to the diversity of structural storage found in persistence today, as well as a mismatch in impedance between the Java application, which employs object-oriented paradigms, and the database, which typically does not support them.
For example, when we have the classic association between two classes, a Car
and its Driver
:
public class Car {
private String plate;
private Driver driver;
}
public class Driver {
private String id;
private String name;
}
Imagine a scenario in which you will return the value from car.driver()
. When we are working with databases, this could mean a JOIN, a subdocument, a user-defined type, or a graph relationship. Thus, this new specification required a common query while also being extensible to several kinds of databases that the specification supports.
In this article, we have seen several aspects of the release of Jakarta EE 11, as well as what we can expect from the future of the Platform. It includes a notable shift in the direction of data, extending beyond cloud-native applications. Jakarta EE is an open-source project under the Eclipse Foundation umbrella, and as usual, you can join and participate. It is mainly because, as Peter Drucker said, “The best way to predict the future is to create it”. Therefore, let’s all participate and help shape the Java enterprise together.