Jakarta EE 11 and the Road Ahead With Jakarta EE 12
Jakarta EE 11 modernizes enterprise Java with record and virtual thread support. Jakarta EE 12 expands this with Jakarta NoSQL, MVC, and the new Jakarta Query.
Join the DZone community and get the full member experience.
Join For FreeJakarta EE 11 is now available, and it’s more than just a version update. It’s the beginning of a new era in enterprise Java—one that aligns with modern Java standards, simplifies the platform, and positions it for the future of cloud-native development. But it doesn’t stop there. Jakarta EE 12 is already shaping up to push the platform even further.
Let’s explore what Jakarta EE 11 delivers and how Jakarta EE 12 is preparing us for a more powerful and modern Java ecosystem.
Even if you’re using Spring Boot, Quarkus, Helidon, or Micronaut, you're relying on Jakarta EE. Examples: Spring Data JPA builds on top of Jakarta Persistence; Quarkus uses Dependency Injection maps to Jakarta CDI; and even Tomcat uses Jakarta Servlet. Jakarta EE is the foundation. The difference is that now it’s modern and lean.
Jakarta EE 11 aligns with Java 17 or higher. That means developers can now rely on features like sealed classes, pattern matching, records, and virtual threads (via Jakarta Concurrency).
This release trims away deprecated APIs, making the platform leaner and more focused on today’s needs.
Among the many improvements, Jakarta CDI becomes the core dependency across the board, moving the platform toward a consistent, type-safe programming model.
One of the most significant evolutions in Jakarta EE 11 is the support for Java records—not just as DTOs, but as first-class citizens in the specification.
Records are concise, immutable, and make significant value objects. And now, they work seamlessly with Jakarta Validation:
public record Car(
@NotBlank @Size(min = 2, max = 50) String model,
@NotBlank @Size(min = 2, max = 50) String licensePlate,
@Min(1886) Integer year
) {}
Jakarta EE 11 also expands record support to Jakarta Persistence. For instance, you can now use a record to define an embeddable engine:
@Embeddable
public record Engine(String type, int horsepower, String manufacturer) {}
@Entity
public class Vehicle {
@Id
@GeneratedValue
private Long id;
private String model;
@Embedded
private Engine engine;
}
Thanks to Java 21’s virtual threads, Jakarta Concurrency is also getting a refresh. This allows asynchronous and parallel operations with minimal thread management overhead:
@Qualifier
@Retention(RUNTIME)
@Target({ FIELD, METHOD, PARAMETER, TYPE })
public @interface FastLane {}
@Qualifier
@Retention(RUNTIME)
@Target({ FIELD, METHOD, PARAMETER, TYPE })
public @interface RegularLane {}
@ManagedExecutorDefinitions({
@ManagedExecutorDefinition(
name = "java:module/concurrent/FastCarExecutor",
context = "java:module/concurrent/FastCarContext",
qualifiers = FastLane.class,
virtual = true,
maxAsync = 10,
hungTaskThreshold = 10000
),
@ManagedExecutorDefinition(
name = "java:module/concurrent/RegularCarExecutor",
context = "java:module/concurrent/RegularCarContext",
qualifiers = RegularLane.class,
virtual = false,
maxAsync = 5,
hungTaskThreshold = 20000
)
})
@ContextServiceDefinition(
name = "java:module/concurrent/FastCarContext",
propagated = {SECURITY, APPLICATION}
)
@ContextServiceDefinition(
name = "java:module/concurrent/RegularCarContext",
propagated = {SECURITY, APPLICATION}
)
public class VirtualThreadConfig {}
@ApplicationScoped
public class VehicleLaneService {
@Inject
@FastLane
ManagedExecutorService fastExecutor;
@Inject
@RegularLane
ManagedExecutorService regularExecutor;
public void processFastLane() {
fastExecutor.execute(() ->
System.out.println("[FAST LANE] Processing vehicle with virtual thread: " + Thread.currentThread())
);
}
public void processRegularLane() {
regularExecutor.execute(() ->
System.out.println("[REGULAR LANE] Processing vehicle with platform thread: " + Thread.currentThread())
);
}
}
public void runWithPlatform() {
platformExecutor.execute(() ->
System.out.println("[PLATFORM] processing car task in: " + Thread.currentThread())
);
}
}
Jakarta Data is the most developer-friendly addition to Jakarta EE 11. It introduces a new, unified API for working with data that simplifies persistence across both relational and NoSQL databases. Built to reduce boilerplate and align with common patterns seen in modern frameworks, such as Spring Data, Jakarta Data provides a type-safe, declarative way to interact with your storage layer—whether you're dealing with SQL, MongoDB, or something in between. It is possible thanks to its deep integration with both Jakarta Persistence and Jakarta NoSQL.
At its core, Jakarta Data provides a flexible model based on repository interfaces. Developers can rely on a hierarchy of built-in repository types, starting from the foundational DataRepository and extending to more focused ones, such as CrudRepository or BasicRepository. These interfaces abstract away the repetitive parts of data access, letting you declare your intent. For example:
@Repository
public interface CarRepository extends BasicRepository<Car, Long> {
List<Car> findByType(CarType type);
Optional<Car> findByName(String name);
}
For more specific needs, Jakarta Data also supports custom repository interfaces tailored to your domain. You can define expressive operations using annotations like @Insert
, @Update
, @Delete
, and @Save
, which makes the repository not just a data access layer, but a domain-driven contract:
@Repository
public interface Garage {
@Insert
Car park(Car car);
}
Pagination is also first-class. Jakarta Data supports both offset-based and cursor-based pagination models. Offset pagination, familiar to anyone who has used SQL's LIMIT/OFFSET
, is ideal for simple and predictable result sets. Cursor-based pagination, on the other hand, is designed for more dynamic datasets—using entity property values as a stable reference point to avoid missed or duplicated results across pages. It makes it a powerful option for real-time applications or feeds where consistency matters.
@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);
}
Looking ahead to Jakarta EE 12, the platform takes an ambitious leap toward embracing modern Java practices and expanding its scope. This version is not just about refinement—it's about inclusion, innovation, and laying the groundwork for a truly unified ecosystem. Here’s what the platform specification includes and introduces:
- Jakarta MVC and Jakarta NoSQL are now part of the Web Profile
- Jakarta Query is introduced as a new specification for object-based querying (unifying SQL, NoSQL, and Data APIs.
Official spec roadmap: jakarta.ee/ja/specifications/platform/12
This release reaffirms the vision that Jakarta EE should not just mirror Java’s progress, but amplify it.
Jakarta EE is finally shedding its legacy skin. With Jakarta EE 11, the platform stepped into modern Java. With Jakarta EE 12, it is embracing the best practices developers want: immutability, lightweight concurrency, dynamic querying, and modular architecture.
Whether you're a Spring developer, a Quarkus early adopter, or a Jakarta native, you benefit from these changes.
This is not just about keeping enterprise Java alive.
It's about making it modern, relevant, and robust again.
Opinions expressed by DZone contributors are their own.
Comments