@NotNull. When Jakarta EE 11 shipped, most of it arrived in your project without a headline.
This article separates what actually changed in those pieces from what merely got a new number. It does that by reading the real API jars — EE 10 next to EE 11 — and Spring Boot’s own version list, rather than the marketing summary. You only need to know that Jakarta EE is a family of standard Java APIs and that Spring uses several of them.
Versions and status. Spring Boot 4.1.1 (Spring Framework 7.0.9, Tomcat 11.0.24, Hibernate 7.4.5), JDK 25.0.4, Maven 3.9. Jakarta jars compared: Servlet 6.0.0→6.1.0, Persistence 3.1.0→3.2.0, Validation 3.0.0→3.1.0, Annotations 2.1.1→3.0.0, Concurrency 3.0.0→3.1.1, plus Data 1.0.2. Companion code: spring-boot-demo/jakarta-ee-11.
Spring Boot picks the Jakarta versions for you
The first thing to understand is that you do not choose the Jakarta API versions in a Spring Boot project; Boot’s dependency list (its “BOM”) does. Reading that list for Boot 4.1.1 answers most “which EE level am I on?” questions in one screen. Servlet 6.1.0, Persistence 3.2.0, Validation 3.1.1 and Annotations 3.0.0 are all EE 11 generation. Tomcat 11.0.24 is the embedded servlet container, Hibernate 7.4.5 the persistence provider. The second half of the output is the surprise: five of the six artifacts checked appear nowhere in the list (Data, Concurrency, CDI, Security, Faces); the single1 is the JAX-RS API.
# 01-boot-managed-jakarta-apis (spring-boot-dependencies 4.1.1)
hibernate-validator.version = 9.1.3.Final
hibernate.version = 7.4.5.Final
jakarta-activation.version = 2.1.4
jakarta-annotation.version = 3.0.0
jakarta-inject.version = 2.0.1
jakarta-jms.version = 3.1.0
jakarta-json-bind.version = 3.0.2
jakarta-json.version = 2.1.3
jakarta-mail.version = 2.1.5
jakarta-management.version = 1.1.4
jakarta-persistence.version = 3.2.0
jakarta-servlet-jsp-jstl.version = 3.0.2
jakarta-servlet.version = 6.1.0
jakarta-transaction.version = 2.0.1
jakarta-validation.version = 3.1.1
jakarta-websocket.version = 2.2.0
jakarta-ws-rs.version = 4.0.0
jakarta-xml-bind.version = 4.0.5
jakarta-xml-soap.version = 3.0.2
jakarta-xml-ws.version = 4.0.3
jetty.version = 12.1.12
spring-data-bom.version = 2026.0.1
spring-framework.version = 7.0.9
tomcat.version = 11.0.24
--- artifacts NOT in the BOM (0 = Spring Boot does not manage them)
jakarta.data-api 0
jakarta.enterprise.concurrent-api 0
jakarta.enterprise.cdi-api 0
jakarta.security.enterprise-api 0
jakarta.faces-api 0
jakarta.ws.rs-api 1
Captured in 01-boot-managed-jakarta-apis.txt.
Read the zeros. A zero means Boot does not manage that artifact: no version is chosen for you and nothing brings it in. Jakarta Data and Jakarta Concurrency are in that group, and two later sections explain what that means in practice.
Going deeper: how the list was produced, and what it does not cover
The capture script downloads the published
spring-boot-dependencies POM for 4.1.1 from Maven Central and prints the version properties that name Jakarta artifacts, Tomcat, Jetty, Hibernate and Spring Data. The second half greps the same POM for four artifact ids.
Two things to keep in mind. The list is exact for 4.1.1 only; Boot 4.2 milestones may differ. And “not managed” is about the BOM; you can still add such a dependency yourself with an explicit version.
- Script: capture-facts.sh
- Reference: Spring Boot dependency versions
Servlet 6.1: the level you are on, proven by asking the server
The servlet API is how a web server hands a request to your code. Spring MVC sits on top of it and hides it, so it is easy to lose track of the version. The module has one endpoint that asks the running container which level it implements:@RestController
class ServletLevelController {
@GetMapping("/servlet-level")
String level(HttpServletRequest request) {
var ctx = request.getServletContext();
return ctx.getMajorVersion() + "." + ctx.getMinorVersion() + " (container: " + ctx.getServerInfo() + ")";
}
}
Source: ServletLevelController.java, lines 8–17.
# 05-servlet-level (Spring Boot 4.1.1, embedded Tomcat, JDK 25)
GET /servlet-level -> 6.1 (container: Apache Tomcat/11.0.24)
Captured in 05-servlet-level.txt.
That output comes from a real Boot 4.1.1 application started in a test: Tomcat 11.0.24, Servlet 6.1. Comparing the 6.0.0 and 6.1.0 jars shows a small change — 26 members added, 3 removed, and one new class,HttpSession.Accessor. Servlet 6.1 is an incremental release; for a Spring MVC application there is nothing to migrate.
--- Servlet: jakarta.servlet-api 6.0.0 -> 6.1.0
classes added : 1 removed: 1
members added : 26 removed: 3
+ class jakarta.servlet.http.HttpSession$Accessor
- class jakarta.servlet.http.Cookie$1
Captured in 02-api-diff.txt.
Going deeper: reading the comparison correctly
The comparison runs
javap -public over every class in both jars and treats each signature as a string, so a changed signature counts as one removal and one addition. That is why “removed: 3” is not the same as “three methods deleted”. The one removed class, Cookie$1, is a compiler-generated inner class rather than API.
I did not list the 26 added members individually; the counts are the claim. If you depend on a specific servlet feature, diff the two jars for that class yourself using the script.
- Endpoint test: ServletLevelTest.java
- All five comparisons: 02-api-diff.txt
Persistence 3.2: the release with real new API
This is the one EE 11 spec where the difference is large: 24 new classes and 251 members added against 39 removed (signature changes counted twice). It is also the only one you may touch directly, through theEntityManager. Hibernate 7 implements it, so you can use these methods today on Boot 4.1.1.
The new classes are mostly options and configuration types: FindOption, LockOption, RefreshOption, PersistenceConfiguration, SchemaManager among them. They show up as extra varargs on existing operations:
# 03-entitymanager-3.2 (methods on jakarta.persistence.EntityManager that 3.1.0 did not have)
public abstract <C, T> T callWithConnection(ConnectionFunction<C, T>);
public abstract <C> void runWithConnection(ConnectionConsumer<C>);
public abstract <T> Query createNativeQuery(String, Class<T>);
public abstract <T> T find(Class<T>, Object, FindOption...);
public abstract <T> T find(EntityGraph<T>, Object, FindOption...);
public abstract <T> T getReference(T);
public abstract <T> TypedQuery<T> createQuery(TypedQueryReference<T>);
public abstract <T> TypedQuery<T> createQuery(criteria.CriteriaSelect<T>);
public abstract CacheRetrieveMode getCacheRetrieveMode();
public abstract CacheStoreMode getCacheStoreMode();
public abstract Query createQuery(criteria.CriteriaDelete<?>);
public abstract Query createQuery(criteria.CriteriaUpdate<?>);
public abstract StoredProcedureQuery createStoredProcedureQuery(String, Class<?>...);
public abstract void lock(Object, LockModeType, LockOption...);
public abstract void refresh(Object, RefreshOption...);
public abstract void setCacheRetrieveMode(CacheRetrieveMode);
public abstract void setCacheStoreMode(CacheStoreMode);
Captured in 03-entitymanager-3.2.txt.
Two groups matter most for everyday code.find(Class, Object, FindOption...), lock(..., LockOption...) and refresh(..., RefreshOption...) let you pass options in the call instead of through a hint map of strings. runWithConnection and callWithConnection hand you the JDBC connection the persistence context uses, in a callback, without unwrapping a Hibernate class.
Additions, not removals. The listing shows method signatures 3.1 did not have; several are new overloads of existing operations, and I did not test whether each old call still behaves identically. The larger point is that these are new methods on an interface that providers implement, which is why the Hibernate 8 article on this site finds its problem in Spring’s persistence-unit class, not in your code.
Going deeper: reading the class list
The class list in
02-api-diff.txt is cut to the first twelve names per spec by the capture script, so the 24 added classes are not all shown. The count is complete; the list is a sample.
This article did not run any of the 3.2 methods against a database; the claim is what the jar declares.
- Full comparison: 02-api-diff.txt
- Related on this site: Hibernate 8 and Jakarta Persistence 4.0: what will break
Validation and Annotations: mostly a version bump, one removal
Validation went from 3.0.0 to 3.1.0 with nine new classes — every one apackage-info — and a single member swapped. In practical terms: no new constraint annotations, nothing to change.
Annotations 3.0.0 is the one with an actual removal: jakarta.annotation.ManagedBean is gone. If your code or a library still uses it, that is a compile error waiting for the day the jar is upgraded.
--- Validation: jakarta.validation-api 3.0.0 -> 3.1.0
classes added : 9 removed: 0
members added : 1 removed: 1
--- Annotations: jakarta.annotation-api 2.1.1 -> 3.0.0
classes added : 2 removed: 1
members added : 0 removed: 1
- class jakarta.annotation.ManagedBean
Captured in 02-api-diff.txt.
Scope of this claim. The comparison covers public signatures only. It cannot see changed behaviour, such as a stricter default in the validation reference implementation. Boot 4.1.1 manages Hibernate Validator 9.1.3.Final, which is a separate release train.
Jakarta Data 1.0: a standard your Spring project does not use
Jakarta Data is the EE 11 answer to what Spring Data popularised: declare a repository interface, get an implementation. The 1.0.2 API defines its own repository types, similar in spirit but not the same interfaces:--- jakarta.data-api 1.0.2: types in jakarta.data.repository
BasicRepository
By
CrudRepository
DataRepository
Delete
Find
Insert
OrderBy
Param
Query
Repository
Save
Update
Captured in 04-data-and-concurrency.txt.
Two checks show where Spring stands. Spring Boot’s dependency list does not managejakarta.data-api (section one, count zero). And the Spring Data jars in the same release train contain no jakarta.data classes at all:
# 06-spring-data-and-jakarta-data (classes mentioning jakarta/data inside Spring Data jars from Spring Data 2026.0.1)
spring-data-commons 4.1.1: entries under jakarta/data/: 0; manifest lines mentioning jakarta.data: 0
spring-data-jpa 4.1.1: entries under jakarta/data/: 0; manifest lines mentioning jakarta.data: 0
Captured in 06-spring-data-and-jakarta-data.txt.
The takeaway. On Spring Boot 4.1.1 you write Spring Data repositories, not Jakarta Data ones, and the two do not interoperate. Jakarta Data matters if you expect to move code between Spring and a Jakarta EE runtime, or read code that uses@Findand@Insert. It is not something to adopt in a Boot project today.
Going deeper: what this does and does not prove
The check shows the Spring Data 4.1.1 commons and JPA jars do not contain Jakarta Data classes and do not mention them in the manifest. It does not prove Spring will never support it, and I did not check other Spring Data modules.
- Check script: capture-facts.sh
- Specification: Jakarta Data 1.0
Concurrency 3.1 and virtual threads: two different switches
Virtual threads are lightweight threads that make blocking code cheap. Two unrelated settings now mention them, and they are easy to confuse. The first is Spring Boot’s own:spring.threads.virtual.enabled. Boot ships it in its configuration metadata as a boolean, default false:
# 07-virtual-threads-property (spring-boot-autoconfigure 4.1.1 configuration metadata)
spring.threads.virtual.enabled (java.lang.Boolean, default False): Whether to use virtual threads.
Captured in 07-virtual-threads-property.txt.
The second is new in Jakarta Concurrency 3.1: the annotation that defines a managed executor gained avirtual attribute (and qualifiers). Comparing 3.0.0 and 3.1.1 shows exactly that difference:
--- jakarta.enterprise.concurrent-api 3.0.0: attributes of @ManagedExecutorDefinition
String name();
String context();
long hungTaskThreshold();
int maxAsync();
--- jakarta.enterprise.concurrent-api 3.1.1: attributes of @ManagedExecutorDefinition
String name();
Class<?>[] qualifiers();
String context();
long hungTaskThreshold();
int maxAsync();
boolean virtual();
Captured in 04-data-and-concurrency.txt.
What I did not test. I did not run a workload with either switch, and I did not check which Boot components consult the property. The claims above are about what the artifacts declare, not about measured behaviour.
So what actually matters?
| EE 11 spec | Change for a Spring Boot 4 project | Action |
|---|---|---|
| Servlet 6.1 | Incremental; Tomcat 11.0.24 implements it (proved by request) | None |
| Persistence 3.2 | Real new API; options on find, lock, refresh, connection callbacks | Use if useful; no migration |
| Validation 3.1 | No new API of substance | None |
| Annotations 3.0 | ManagedBean removed | Grep your code and libraries |
| Data 1.0 | Separate repository model; not used by Spring Data | Ignore unless moving between runtimes |
| Concurrency 3.1 | virtual attribute; jar not managed by Boot | Use Boot’s property instead |
Should you care about Jakarta EE 11? Only about one thing in the list: the Persistence 3.2 additions, which you can use today. Everything else is either invisible or something Spring Boot does not adopt. What this article cannot tell you is how a full Jakarta EE runtime behaves; it looked only at the APIs a Spring Boot project actually meets.
Further reading
- Companion code: spring-boot-demo/jakarta-ee-11
- Jakarta EE Platform 11
- Jakarta Persistence 3.2 and Jakarta Concurrency 3.1
- Related on this site: Hibernate 8 and Jakarta Persistence 4.0 preview; Spring Boot 4.2 preview
No Comments yet!