Java’s Integer and LocalDate lose their identity in JDK 28 previews, and patched Java runtimes may still trust compromised eMudhra certificates.
This digest covers 20 to 28 October 2026.
Java
1. Integer and LocalDate lose their identity in JDK 28 previews (JEP 401)
Every Java object has an identity: two objects with the same fields are still two objects, and == tells them apart. Value classes give that up so the JVM can store them flat, like primitives. JEP 401 brings them to JDK 28 as a preview, and OpenJDK’s Quality Outreach asked library maintainers to test now.
With preview features on, 30 JDK classes become value classes. Integer and LocalDate are among them, even in code compiled on older JDKs:
var x = LocalDate.of(2026, 7, 31);
var y = LocalDate.of(2026, 7, 31);
var identical = x == y;
// without value classes: false
// with value classes: trueSynchronizing on these objects or using them as WeakHashMap keys throws IdentityException. Record constructors that call instance methods stop compiling once you build with preview features. Without the preview flag, JDK 28 should behave as before, but OpenJDK asks you to test that too. Run your tests on the latest JDK 28 early access build with and without it:
java --enable-preview -jar app.jarJDK 28 is planned for 9 March 2027, with the feature freeze on 3 December.
Sources: Inside Java, Nicolai Parlog, JEP 401, JDK 28 early access, JDK 28 project page.
2. Small off-heap allocations get 6 to 18x faster
The Foreign Function & Memory API calls native code from Java. A call usually allocates a few bytes of native memory in a confined arena, which only one thread can use, and frees them when the arena closes. Each allocation used to go to the native allocator. In JDK 28 small allocations come from a per-thread pool instead, with no source change:
try (Arena arena = Arena.ofConfined()) {
MemorySegment result = arena.allocate(ValueLayout.JAVA_INT);
nativeFunction.invokeExact(result);
return result.get(ValueLayout.JAVA_INT, 0);
}On an Apple M4, a 5-byte allocation fell from 15.9 to 1.05 ns, about 15x. Pools hold 64 bytes, so larger allocations run as before. It helps most where a binding opens a confined arena around each native call, such as jextract-style wrappers.
Source: Inside Java, Per-Ake Minborg.
3. Spring Boot 4.2.0-RC1
The first Spring Boot 4.2 release candidate came out on 23 October with 113 changes. It moves to Spring Framework 7.1 and Spring Security 7.2 (see item 15). The changes you notice first:
- Buildpacks images now build on Ubuntu 26.04 instead of 24.04 by default.
- The properties migrator gets an opt-in setting that fails startup on errors or warnings, so you catch renamed properties in CI.
- Startup is faster: classes and resources load straight from nested jars.
- Caffeine caches can run in async mode.
To keep the old builder while you upgrade, set it in the plugin configuration:
<!-- pom.xml, spring-boot-maven-plugin -->
<configuration>
<image>
<builder>paketobuildpacks/builder-noble-java-tiny</builder>
</image>
</configuration>// build.gradle.kts
tasks.bootBuildImage {
builder = "paketobuildpacks/builder-noble-java-tiny"
}Spring Cloud 2026.0.0-RC1 is built on it, and Spring Cloud Bus is now part of Spring Cloud Stream with the same artifact coordinates. Bug-fix releases 4.1.2 and 4.0.9 also came out.
Sources: Spring Boot 4.1.2, Spring Boot 4.0.9, Spring blog, Moritz Halbritter, 4.2.0-RC1 release notes, Spring Cloud 2026.0.0-RC1.
4. Spring AI 2.1 keeps model reasoning between calls
Reasoning models return thinking blocks and signed reasoning items, and the provider wants them back unchanged and in order on the next call. Spring AI 2.0 stored a message as text plus side lists, so reasoning got lost in streaming and chat memory. Requests could then fail with HTTP 400 or lose context.
In 2.1.0-RC1 a message is an ordered list of parts: text, reasoning, tool calls and more. A new session API keeps every part across calls, and across restarts with its JDBC store:
String answer = chatClient.prompt()
.user("Find the cheapest flight to Lisbon next Friday")
.advisors(SessionAdvisors.session("session-42"))
.call()
.content();Register SessionAdvisors.builder(sessionService) as a default advisor on the ChatClient first. Every call needs a session id: there is no default session.
ChatMemory is deprecated for removal in 3.0. Tools marked as requiring confirmation now pause until your app approves the call.
Sources: Spring blog, Eric Bottard, Spring blog, Dimitar Proynov.
5. TornadoVM 8 drops the Graal compiler
TornadoVM runs Java methods on GPUs. It turns your bytecode into GPU kernels and copies data between the Java heap and the device. Version 8.0 replaces the Graal compiler with TC, TornadoVM’s own bytecode-to-kernel compiler, and moves to the Apache 2.0 license.
The 7.x line had already swapped JNI for the FFM API and dropped the JVMCI dependency, so TornadoVM now runs on a stock JDK 21, 25, 26 or 27.
Sources: TornadoVM 8.0.0 release, InfoQ, Ben Evans.
6. WildFly 42 runs Jakarta Concurrency on virtual threads
WildFly is an open source Jakarta EE application server. Version 42 can back Jakarta Concurrency resources, the managed executors your EE code submits tasks to, with virtual threads. Every installation now ships a CycloneDX SBOM, so a scanner knows what is inside.
Java 25 is the recommended runtime. WildFly 43 is the last release on Java 17; WildFly 44 moves the baseline to 21. Plan the JDK upgrade now if you are still on 17.
Source: WildFly blog, Brian Stansberry.
7. Smaller releases
- Apache POI 6.0.0: needs JDK 17, up from 8. DataFormatter now shows cached formula values by default, and some deprecated methods are gone.
- Spring Modulith 2.2 RC1: Modulith checks and documents the module boundaries inside one Spring Boot app. In 2.2 RC1 an event is marked as completed inside the listener’s transaction, so both succeed or fail together.
Sources: Apache POI changes, Spring Modulith blog.
8. Worth reading
- What jstack misses when your Java service uses virtual threads by Felipe Maschio: the dump shows idle carriers while 5,000 requests wait for a database connection.
- What’s going wrong with my JVM?, a first-failure diagnosis guide on Inside Java.
- Java Secure Coding Guidelines, Andrew Gross’s talk on the updated input validation and serialization advice.
- Haskell-like type class resolution in Java by Gabriel Garcia: how far Java’s generics go when you emulate Haskell’s type classes.
Kotlin
9. Kotlin 2.5.0-Beta2 brings full value classes
Kotlin’s value classes could hold one property and existed to wrap a value without the cost of an object. Kotlin 2.5.0-Beta2 adds full value classes as an experimental feature: any number of properties, no identity, like JEP 401 in Java.
value class Complex(val real: Double, val imaginary: Double)
sealed interface UserResponse
value class UserFound(val user: User) : UserResponse
value object UserUnknown : UserResponseThe compiler generates the data class methods except copy and componentN. Full value classes can be sealed or abstract. Turn them on with a compiler flag. The flags in items 10 and 11 go in the same place:
<!-- pom.xml, kotlin-maven-plugin -->
<configuration>
<args>
<arg>-Xfull-value-classes</arg>
</args>
</configuration>// build.gradle.kts
kotlin {
compilerOptions {
freeCompilerArgs.add("-Xfull-value-classes")
}
}A plain value class will no longer be inline. A one-property inline class will need the inline modifier or @JvmInline. Kotlin 2.5.20 warns about unmarked ones, and a later release turns the warning into an error. JVM code already needs @JvmInline, so multiplatform code is where you look first.
Source: What’s new in Kotlin 2.5.0-Beta2.
10. Companion blocks and companion extensions
A companion object holds a class’s static-like members, and on the JVM each needs @JvmStatic to become a real static. Beta2 adds companion blocks, whose members compile to statics, and companion extensions, which work even when the class has no companion object:
companion val Vector.UnitX get() = Vector(1.0, 0.0)
data class Vector(val x: Double, val y: Double) {
companion {
val Zero: Vector get() = Vector(0.0, 0.0)
}
}Both are experimental and need a flag:
kotlinc -Xcompanion-blocks-and-extensions Vector.ktSource: What’s new in Kotlin 2.5.0-Beta2.
11. The Kotlin 2.5 compiler needs JDK 17 to run
Kotlin 2.5.0-Beta2 stops with an error when the compiler runs on a JDK older than 17. This is the JDK that runs the compiler, not your bytecode target. Until 2.5.20 or 2.6.0 makes JDK 17 mandatory, a flag turns the error off:
kotlinc -Xallow-pre-17-runtime-jdk Vector.ktCI images still on an older JDK break with the upgrade. The same beta can generate Java 28 bytecode. Kotlin 2.5.0 is planned for December.
Sources: What’s new in Kotlin 2.5.0-Beta2, Kotlin releases.
12. Swift export goes Beta
Kotlin Multiplatform shares Kotlin code with iOS. Swift export generates Swift from that code directly, without the Objective-C header in between. In 2.5.0-Beta2 it is Beta with a new Gradle DSL: the swiftExport block becomes export with a swift block inside, and dependencies you declare with api are exported on their own.
Library authors can try a new publication format, where each platform artifact (except JVM and Android) is a single archive. The cryptography-kotlin library went from 7,539 files and 91 MB to 1,036 files and 23 MB.
Source: What’s new in Kotlin 2.5.0-Beta2.
13. Smaller news
- Kotlin DataFrame 1.0.0-rc02: the typed table library for data work in Kotlin reaches its final release candidate before 1.0 (release).
- Bootiful Kotlin: Josh Long builds a Spring Boot backend in Kotlin.
- Flake-free Flow tests: Touchlab on why coroutine tests break with real threads and dispatchers.
- Deezer’s Kotlin Multiplatform case study: one codebase for phones, browsers, cars and watches.
- The KotlinConf 2027 call for papers closes on 27 November.
Security
14. eMudhra: patch your Java runtime again
A certificate authority (CA) signs the certificates your TLS clients trust. On 17 October a researcher showed that the private keys of eight eMudhra intermediate CAs could be recovered from the certificates themselves. Whoever holds them can issue a certificate for any site, so a man-in-the-middle can fool any Java client that trusts these CAs.
Browsers blocked the certificates within days, but Java clients rarely check revocation. The first JDK fixes blocked the intermediates by fingerprint. Sonatype then showed that a re-encoded copy of a blocked certificate gets a new fingerprint and still validates. OpenJDK updated its advice on 24 October: act now, “even if you have updated your runtime in the past few days”.
The preferred fix is a runtime whose release notes list JDK-8395690. Until your vendor ships one, remove the affected roots. Removing the Comodo AAA root can also break services with certificates from it, so test those connections first. On JDK 11 and later:
keytool -delete -cacerts -alias "emsignrootcag1 [jdk]"
keytool -delete -cacerts -alias "emsignrootcag2 [jdk]"
keytool -delete -cacerts -alias "emsigneccrootcag3 [jdk]"
keytool -delete -cacerts -alias "comodoaaaca [jdk]"Ignore errors that an alias does not exist, and restart every running JVM afterwards. On JDK 8, point keytool at the keystore under your JDK’s install directory ($JDK_HOME below), the same way for all four aliases:
keytool -delete -keystore $JDK_HOME/jre/lib/security/cacerts -alias "emsignrootcag1 [jdk]"On Debian and Ubuntu, updating the ca-certificates packages doesn’t refresh Java’s keystore. Rebuild it as root:
rm -f /etc/ssl/certs/java/cacerts && update-ca-certificates --freshOpenJDK’s page has a CheckCerts program that tells you whether a runtime is still at risk.
Sources: OpenJDK guidance, OpenJDK advisory, Sonatype research, reporter’s incident page.
15. Locked accounts still log in with a one-time token
One-time token login is Spring Security’s magic link: the user gets a link with a token and logs in without a password. The password provider rejects locked and disabled accounts. The one-time token provider never checked them, so a user you banned yesterday can log in with a magic link today:
class MagicLinkLogin {
Authentication login() {
var users = new InMemoryUserDetailsManager(User.withUsername("alice")
.password("{noop}secret").roles("USER")
.accountLocked(true) // banned yesterday
.build());
var tokens = new InMemoryOneTimeTokenService();
var token = tokens.generate(new GenerateOneTimeTokenRequest("alice"));
var provider = new OneTimeTokenAuthenticationProvider(tokens, users);
// Spring Security 7.1: no account status check, alice gets in
return provider.authenticate(
OneTimeTokenAuthenticationToken.unauthenticated(token.getTokenValue()));
}
}Spring Security 7.2.0-RC1 adds the check, but it stays off by default to keep the old behavior. Turn it on yourself:
@Bean
OneTimeTokenAuthenticationProvider oneTimeTokenAuthenticationProvider(
OneTimeTokenService tokens, UserDetailsService users) {
var provider = new OneTimeTokenAuthenticationProvider(tokens, users);
provider.setUserDetailsChecker(new AccountStatusUserDetailsChecker());
return provider;
}On 7.1 and older, check the account in your token generation handler before you send the link.
Sources: Spring Security 7.2 what’s new, one-time token docs, issue 17655.
16. Patch list
- Java October Critical Patch Update: JDK 27.0.1, 25.0.5, 21.0.13, 17.0.21, 11.0.33 and 8u512. OpenJDK’s eMudhra advisory lists these builds as affected (CVSS 9.1), so take the next build whose release notes list JDK-8395690 (item 14).
- Micronaut 5.2.3: a security release for Micronaut Core and Micronaut Security. On Micronaut 4, update to Platform 4.10.22; on Micronaut 3, to Core 3.10.17.
Sources: OpenJDK advisory, BellSoft release notes, Micronaut 5.2.3.
Do you use magic links in your Spring apps, and did you check what they do with locked accounts?
At the end
If you like this digest, follow me on LinkedIn and Telegram to get the next one. The previous digest: JDK 28 gets structured concurrency and AOT code.
Found this useful? Follow me on social media to stay updated.
