Add jakarta-ee-11 module: Jakarta EE 10 vs 11 API comparison against Spring Boot 4.1.1
Co-Authored-By: Claude Sonnet 5 <[email protected]> Claude-Session: https://claude.ai/code/session_01Gth99spB1CzQmBFofXmU4Z
This commit is contained in:
@@ -0,0 +1,29 @@
|
||||
# jakarta-ee-11
|
||||
|
||||
Companion module for the ankurm.com post *Jakarta EE 11 for Spring Developers: What Actually Matters*.
|
||||
|
||||
Reads the Jakarta EE 10 and 11 API jars from Maven Central with `javap`, reads Spring Boot 4.1.1's own BOM and configuration metadata, and runs one tiny Boot application to ask the embedded Tomcat which Servlet level it implements. No claim in the post comes from prose.
|
||||
|
||||
| Component | Version |
|
||||
|---|---|
|
||||
| Spring Boot | 4.1.1 |
|
||||
| JDK | 25 |
|
||||
| Maven | 3.9 |
|
||||
|
||||
## Run
|
||||
|
||||
```bash
|
||||
./scripts/run-all.sh # regenerates everything under output/
|
||||
```
|
||||
|
||||
## Output
|
||||
|
||||
| File | Shows |
|
||||
|---|---|
|
||||
| `output/01-boot-managed-jakarta-apis.txt` | Jakarta versions Spring Boot 4.1.1 manages, and four artifacts it does not |
|
||||
| `output/02-api-diff.txt` | Class and member counts, EE 10 vs EE 11, for Servlet, Persistence, Validation, Annotations, Concurrency |
|
||||
| `output/03-entitymanager-3.2.txt` | New `EntityManager` methods in Persistence 3.2 |
|
||||
| `output/04-data-and-concurrency.txt` | Jakarta Data 1.0.2 repository types; `@ManagedExecutorDefinition` in 3.0.0 vs 3.1.1 |
|
||||
| `output/05-servlet-level.txt` | The running application's Servlet level (written by `ServletLevelTest`) |
|
||||
| `output/06-spring-data-and-jakarta-data.txt` | Spring Data 4.1.1 jars contain no `jakarta.data` types |
|
||||
| `output/07-virtual-threads-property.txt` | Boot's `spring.threads.virtual.enabled` property, from shipped metadata |
|
||||
@@ -0,0 +1,32 @@
|
||||
# 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
|
||||
@@ -0,0 +1,45 @@
|
||||
# 02-api-diff (javap -public over every class of the two jars; members are signature strings, so a changed signature counts as one removed and one added)
|
||||
--- 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
|
||||
--- Persistence: jakarta.persistence-api 3.1.0 -> 3.2.0
|
||||
classes added : 24 removed: 0
|
||||
members added : 251 removed: 39
|
||||
+ class jakarta.persistence.CheckConstraint
|
||||
+ class jakarta.persistence.ConnectionConsumer
|
||||
+ class jakarta.persistence.ConnectionFunction
|
||||
+ class jakarta.persistence.EnumeratedValue
|
||||
+ class jakarta.persistence.FindOption
|
||||
+ class jakarta.persistence.Graph
|
||||
+ class jakarta.persistence.LockOption
|
||||
+ class jakarta.persistence.PersistenceConfiguration
|
||||
+ class jakarta.persistence.PersistenceUnitTransactionType
|
||||
+ class jakarta.persistence.RefreshOption
|
||||
+ class jakarta.persistence.SchemaManager
|
||||
+ class jakarta.persistence.SchemaValidationException
|
||||
--- Validation: jakarta.validation-api 3.0.0 -> 3.1.0
|
||||
classes added : 9 removed: 0
|
||||
members added : 1 removed: 1
|
||||
+ class jakarta.validation.bootstrap.package-info
|
||||
+ class jakarta.validation.constraints.package-info
|
||||
+ class jakarta.validation.constraintvalidation.package-info
|
||||
+ class jakarta.validation.executable.package-info
|
||||
+ class jakarta.validation.groups.package-info
|
||||
+ class jakarta.validation.metadata.package-info
|
||||
+ class jakarta.validation.package-info
|
||||
+ class jakarta.validation.spi.package-info
|
||||
+ class jakarta.validation.valueextraction.package-info
|
||||
--- Annotations: jakarta.annotation-api 2.1.1 -> 3.0.0
|
||||
classes added : 2 removed: 1
|
||||
members added : 0 removed: 1
|
||||
+ class jakarta.annotation.package-info
|
||||
+ class jakarta.annotation.security.package-info
|
||||
- class jakarta.annotation.ManagedBean
|
||||
--- Concurrency: jakarta.enterprise.concurrent-api 3.0.0 -> 3.1.1
|
||||
classes added : 3 removed: 0
|
||||
members added : 15 removed: 0
|
||||
+ class jakarta.enterprise.concurrent.Schedule
|
||||
+ class jakarta.enterprise.concurrent.package-info
|
||||
+ class jakarta.enterprise.concurrent.spi.package-info
|
||||
@@ -0,0 +1,18 @@
|
||||
# 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);
|
||||
@@ -0,0 +1,27 @@
|
||||
# 04-data-and-concurrency
|
||||
--- jakarta.data-api 1.0.2: types in jakarta.data.repository
|
||||
BasicRepository
|
||||
By
|
||||
CrudRepository
|
||||
DataRepository
|
||||
Delete
|
||||
Find
|
||||
Insert
|
||||
OrderBy
|
||||
Param
|
||||
Query
|
||||
Repository
|
||||
Save
|
||||
Update
|
||||
--- 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();
|
||||
@@ -0,0 +1,2 @@
|
||||
# 05-servlet-level (Spring Boot 4.1.1, embedded Tomcat, JDK 25)
|
||||
GET /servlet-level -> 6.1 (container: Apache Tomcat/11.0.24)
|
||||
@@ -0,0 +1,3 @@
|
||||
# 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
|
||||
@@ -0,0 +1,2 @@
|
||||
# 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.
|
||||
@@ -0,0 +1,44 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<parent>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-parent</artifactId>
|
||||
<version>4.1.1</version>
|
||||
<relativePath/>
|
||||
</parent>
|
||||
|
||||
<groupId>com.ankurm</groupId>
|
||||
<artifactId>jakarta-ee-11</artifactId>
|
||||
<version>1.0.0</version>
|
||||
<name>jakarta-ee-11</name>
|
||||
<description>Which Jakarta EE 11 specifications a Spring Boot 4.1 application actually uses</description>
|
||||
|
||||
<properties>
|
||||
<java.version>25</java.version>
|
||||
</properties>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-webmvc</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-webmvc-test</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
<build>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-maven-plugin</artifactId>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
</project>
|
||||
Executable
+81
@@ -0,0 +1,81 @@
|
||||
#!/usr/bin/env bash
|
||||
# Facts about the Jakarta EE 11 API jars, read from Maven Central artifacts and Spring Boot's own BOM. No running application.
|
||||
set -uo pipefail
|
||||
cd "$(dirname "$0")/.."
|
||||
R="$HOME/.m2/repository"; W=$(mktemp -d); CEN=https://repo1.maven.org/maven2
|
||||
get() { mvn -B -q dependency:get -Dartifact="$1" >/dev/null 2>&1; }
|
||||
jar() { echo "$R/$(echo "$1" | cut -d: -f1 | tr . /)/$2/$3/$2-$3.jar"; }
|
||||
|
||||
# --- 01: what Spring Boot 4.1.1 manages -------------------------------------------------------------------
|
||||
for i in 1 2 3 4 5; do code=$(curl -s -o "$W/bom.pom" -w '%{http_code}' $CEN/org/springframework/boot/spring-boot-dependencies/4.1.1/spring-boot-dependencies-4.1.1.pom); [ "$code" = 200 ] && break; sleep $((i*3)); done
|
||||
{
|
||||
echo "# 01-boot-managed-jakarta-apis (spring-boot-dependencies 4.1.1)"
|
||||
grep -E '<(jakarta-[a-z-]+|tomcat|jetty|hibernate|hibernate-validator|spring-data-bom|spring-framework)\.version>' "$W/bom.pom" | sed 's/^ *//; s/<\([^>]*\)>\(.*\)<\/.*/\1 = \2/' | sort
|
||||
echo "--- artifacts NOT in the BOM (0 = Spring Boot does not manage them)"
|
||||
for a in jakarta.data-api jakarta.enterprise.concurrent-api jakarta.enterprise.cdi-api jakarta.security.enterprise-api jakarta.faces-api jakarta.ws.rs-api; do
|
||||
printf ' %-36s %s\n' "$a" "$(grep -c "<artifactId>$a</artifactId>" "$W/bom.pom")"
|
||||
done
|
||||
} > output/01-boot-managed-jakarta-apis.txt
|
||||
|
||||
# --- 02: API differences between the EE 10 and EE 11 versions of four specs ---------------------------------
|
||||
diffspec() { # name group artifact old new
|
||||
get "$2:$3:$4"; get "$2:$3:$5"
|
||||
for v in $4 $5; do
|
||||
j=$(jar "$2" "$3" "$v"); rm -rf "$W/x-$v"; mkdir -p "$W/x-$v"; unzip -q -o "$j" '*.class' -d "$W/x-$v" 2>/dev/null
|
||||
( cd "$W/x-$v" && find . -name '*.class' ! -name 'module-info.class' | sed 's#^\./##; s#\.class$##; s#/#.#g' | sort > "$W/cls-$v.txt"
|
||||
javap -public -cp . $(cat "$W/cls-$v.txt" | grep -v 'package-info') 2>/dev/null | grep -E '^\s+(public|protected)|^(public|protected)' | sed 's/^ *//' | sort -u > "$W/mem-$v.txt" )
|
||||
done
|
||||
echo "--- $1: $3 $4 -> $5"
|
||||
echo " classes added : $(comm -13 "$W/cls-$4.txt" "$W/cls-$5.txt" | wc -l) removed: $(comm -23 "$W/cls-$4.txt" "$W/cls-$5.txt" | wc -l)"
|
||||
echo " members added : $(comm -13 "$W/mem-$4.txt" "$W/mem-$5.txt" | wc -l) removed: $(comm -23 "$W/mem-$4.txt" "$W/mem-$5.txt" | wc -l)"
|
||||
comm -13 "$W/cls-$4.txt" "$W/cls-$5.txt" | sed 's/^/ + class /' | head -12
|
||||
comm -23 "$W/cls-$4.txt" "$W/cls-$5.txt" | sed 's/^/ - class /' | head -12
|
||||
}
|
||||
{
|
||||
echo "# 02-api-diff (javap -public over every class of the two jars; members are signature strings, so a changed signature counts as one removed and one added)"
|
||||
diffspec Servlet jakarta.servlet jakarta.servlet-api 6.0.0 6.1.0
|
||||
diffspec Persistence jakarta.persistence jakarta.persistence-api 3.1.0 3.2.0
|
||||
diffspec Validation jakarta.validation jakarta.validation-api 3.0.0 3.1.0
|
||||
diffspec Annotations jakarta.annotation jakarta.annotation-api 2.1.1 3.0.0
|
||||
diffspec Concurrency jakarta.enterprise.concurrent jakarta.enterprise.concurrent-api 3.0.0 3.1.1
|
||||
} > output/02-api-diff.txt
|
||||
|
||||
# --- 03: the new Persistence 3.2 EntityManager methods, by name --------------------------------------------
|
||||
{
|
||||
echo "# 03-entitymanager-3.2 (methods on jakarta.persistence.EntityManager that 3.1.0 did not have)"
|
||||
for v in 3.1.0 3.2.0; do javap -public -cp "$(jar jakarta.persistence jakarta.persistence-api $v)" jakarta.persistence.EntityManager 2>/dev/null | grep '(' | sed 's/^ *//; s/jakarta.persistence.//g; s/java.lang.//g; s/java.util.//g' | sort > "$W/em-$v.txt"; done
|
||||
comm -13 "$W/em-3.1.0.txt" "$W/em-3.2.0.txt"
|
||||
} > output/03-entitymanager-3.2.txt
|
||||
|
||||
# --- 04: Jakarta Data 1.0 API surface and the virtual-thread switch in Concurrency 3.1 ---------------------
|
||||
get jakarta.data:jakarta.data-api:1.0.2
|
||||
{
|
||||
echo "# 04-data-and-concurrency"
|
||||
echo "--- jakarta.data-api 1.0.2: types in jakarta.data.repository"
|
||||
unzip -l "$(jar jakarta.data jakarta.data-api 1.0.2)" | awk '{print $4}' | grep -E '^jakarta/data/repository/[A-Z][A-Za-z]*\.class$' | sed 's#jakarta/data/repository/##; s#\.class##' | sort | sed 's/^/ /'
|
||||
for v in 3.0.0 3.1.1; do
|
||||
echo "--- jakarta.enterprise.concurrent-api $v: attributes of @ManagedExecutorDefinition"
|
||||
javap -public -cp "$(jar jakarta.enterprise.concurrent jakarta.enterprise.concurrent-api $v)" jakarta.enterprise.concurrent.ManagedExecutorDefinition 2>/dev/null | grep '()' | sed 's/^ *//; s/public abstract //; s/java.lang.//g' | sed 's/^/ /'
|
||||
done
|
||||
} > output/04-data-and-concurrency.txt
|
||||
|
||||
# --- 06: does the Spring Data version Boot manages contain any Jakarta Data code? ---------------------------
|
||||
{
|
||||
echo "# 06-spring-data-and-jakarta-data (classes mentioning jakarta/data inside Spring Data jars from Spring Data 2026.0.1)"
|
||||
for a in spring-data-commons spring-data-jpa; do
|
||||
v=$(curl -s $CEN/org/springframework/data/$a/maven-metadata.xml | grep -o '<version>4\.[0-9.]*</version>' | tail -1 | sed 's/<[^>]*>//g')
|
||||
get org.springframework.data:$a:$v
|
||||
j="$R/org/springframework/data/$a/$v/$a-$v.jar"
|
||||
n=$(unzip -l "$j" 2>/dev/null | grep -c 'jakarta/data/')
|
||||
m=$(unzip -p "$j" 2>/dev/null META-INF/MANIFEST.MF | grep -c 'jakarta.data')
|
||||
echo " $a $v: entries under jakarta/data/: $n; manifest lines mentioning jakarta.data: $m"
|
||||
done
|
||||
} > output/06-spring-data-and-jakarta-data.txt
|
||||
|
||||
# --- 07: Spring Boot's own switch for virtual threads (read from the shipped configuration metadata) ----------
|
||||
get org.springframework.boot:spring-boot-autoconfigure:4.1.1
|
||||
{
|
||||
echo "# 07-virtual-threads-property (spring-boot-autoconfigure 4.1.1 configuration metadata)"
|
||||
unzip -p "$(jar org.springframework.boot spring-boot-autoconfigure 4.1.1)" META-INF/spring-configuration-metadata.json \
|
||||
| python3 -c "import json,sys; [print(p['name'], '(' + p.get('type','?') + ', default ' + str(p.get('defaultValue','none')) + '):', p.get('description','')) for p in json.load(sys.stdin)['properties'] if p['name']=='spring.threads.virtual.enabled']"
|
||||
} > output/07-virtual-threads-property.txt
|
||||
Executable
+7
@@ -0,0 +1,7 @@
|
||||
#!/usr/bin/env bash
|
||||
# Regenerates every file under output/. Needs JDK 25, Maven 3.9 and network access to Maven Central.
|
||||
set -euo pipefail
|
||||
cd "$(dirname "$0")/.."
|
||||
mvn -B test # 05-servlet-level.txt
|
||||
./scripts/capture-facts.sh # 01-04, 06, 07
|
||||
ls -1 output
|
||||
@@ -0,0 +1,7 @@
|
||||
package com.ankurm.jee11;
|
||||
|
||||
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||
|
||||
@SpringBootApplication
|
||||
public class Jee11Application {
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
package com.ankurm.jee11;
|
||||
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
/** Asks the servlet container which Servlet specification 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() + ")";
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
package com.ankurm.jee11;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.List;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.springframework.boot.test.context.SpringBootTest;
|
||||
import org.springframework.boot.test.web.server.LocalServerPort;
|
||||
import org.springframework.web.client.RestClient;
|
||||
|
||||
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
|
||||
class ServletLevelTest {
|
||||
|
||||
@LocalServerPort
|
||||
int port;
|
||||
|
||||
@Test
|
||||
void containerReportsServlet61() throws IOException {
|
||||
String body = RestClient.create("http://localhost:" + port).get().uri("/servlet-level").retrieve().body(String.class);
|
||||
Files.createDirectories(Path.of("output"));
|
||||
Files.write(Path.of("output/05-servlet-level.txt"), List.of(
|
||||
"# 05-servlet-level (Spring Boot 4.1.1, embedded Tomcat, JDK " + Runtime.version().feature() + ")",
|
||||
"GET /servlet-level -> " + body));
|
||||
assertThat(body).startsWith("6.1");
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user