Abstract
Java (and the JVM) reaches the next long-term-support version v25 in September 2025 (it will be 30 years old!) and that warrants an exploration of its modern features. Despite the availability of newer technologies and languages like Kotlin, Scala, Clojure, and others like Go and Rust, Java still dominates many large codebases and sits 4th on the TIOBE index1 of programming languages. Rumors of the death of Java may be unfounded. What better way to discover and explore what’s new in a hands-on way than to over-engineer and overcomplicate the age-old game of tic-tac-toe2!
Java: Loved and Hated
Quote
”There are only two kinds of languages: the ones people complain about and the ones nobody uses” - Bjarne Stroustrup (Creator of C++)
For all of the complaints, there’s still a lot to love about Java. It has earned a place on the the Mount Rushmore of programming languages and when its time comes to pass the torch to a truly worthy general purpose language successor it will but that time is not now, nor will it be for some time. If you’re an experienced Java developer who had to code through years of stagnant features and are were enviously at other languages or even a new engineer wondering if there’s life in the old beast yet, come and take a sip as we explore the new features and enhancements that have come and are still coming thick and fast over recent years.
Tic-Tac-Toe
Tic-tac-toe is a simple game usually played by two players who take turns marking the spaces in a three-by-three grid with X or O. The player who successfully places three of their marks in either a horizontal, vertical, or diagonal row is the winner.
Our tic-tac-toe repository started off with a simple JDK 16-compatible implementation and is available here: https://github.com/briancorbinxyz/overengineering-tictactoe/tree/jdk16 with minimal support for and X by X game of tic-tac-toe using a one-dimensional array with sequential checks for a winner between a human vs. unintelligent (read: it picks a spot at random) bot.
As we update our code we (at least mostly) use the typical OpenJDK reference implementation from jdk.java.net via SDKMan3 though we may dip into some performance implementations like Azul Builds for comparison. We’ll also focus primarily on finalized features rather than those still in preview prior to v25. We will also introduce best practices as the code base grows.
Introduction to JDK 17
JDK 17 introduced a number of interesting features4, including Sealed Classes, kicking off improvements to Switch pattern matching. It also removed/deprecated a few that I can’t imagine anyone will miss like Applet API support5, the Security Manager6 the latter of which was a pain to use and for all the effort failed to provide protection against 80% of the top security threats7, as well as RMI8 (remote-method-invocation) which has effectively been replaced with web-technologies and micro-services.
Features
Sealed Classes
Sealed Classes9 offer more control over the class hierarchy, enabling you to specify which classes or interfaces can extend or implement them. This allows you to maintain a controlled and predictable class hierarchy which is especially useful for exhaustive pattern matching and reflection.
Example
In our game of tic-tac-toe we had a simple interface Player
which could be one of HumanPlayer
or BotPlayer
. We can seal the hierarchy by using the permits
clause, and therefore disallowing any new extensions like AlienPlayer
, or JoePlayer
10 making it possible to reason on our fixed set of known Player
types.
public sealed interface Player permits HumanPlayer, BotPlayer {
String getPlayerMarker();
int nextMove(GameBoard board);
}
This means that the classes themselves also need to be sealed by making them final
:
The class HumanPlayer with a sealed direct superclass or a sealed direct superinterface Player should be declared either final, sealed, or non-sealed
public final class BotPlayer implements Player {
...
}
public final class HumanPlayer implements Player {
...
}
Now, if someone tries to extend Player
(directly) with a new type not included in our permits
clause there will be an error:
The type AlienPlayer that implements a sealed interface Player should be a permitted subtype of Player
Altogether, this method of sealing the hierarchy helps us to abide by the principle “design for extension, or else prohibit it.”11 which, in practice, is more pragmatic than the open-closed principle12, certainly at the class-level.
Enhanced Pseudo-Random Number Generators
Essentially, prior to JDK 17 the random number generators were not easily swappable and didn’t take advantage of newer features and programming models in java like stream processing. Various random number generators are available that provide enhanced statistical levels of random distribution, better quality, scalability, or the ability to hop/skip/jump over generated numbers, all of these now implement the RandomGenerator
interface and if we desire we can create our own.
Example
In our game of tic-tac-toe our bot (BotPlayer
) currently selects a random available location for its next move. Swapping out the field:
private final Random random;
for:
private final RandomGenerator random;
allows us to code to the new interface. It wouldn’t be over-engineered, though, if we didn’t go the lengths of making it cryptographically secure by default and specifiable in its constructor.
public BotPlayer(String playerMarker, RandomGenerator randomGenerator) {
this.playerMarker = playerMarker;
this.random = randomGenerator;
}
public BotPlayer(String playerMarker) {
this(playerMarker, new SecureRandom());
}
This is less over-engineered for this case than grabbing a stream of random.ints()
upfront and taking the first valid move (int
) it spits out (don’t tempt me - I’ll do it!).
AOT Compilation Removal
The experimental AOT (ahead-of-time) compiler GRAAL was removed13 from the standard build because it was a lot of a work to maintain vs. the usage it was getting. We may want to still be able to use this feature to be able to start our applications more quickly or take advantage of other benefits of ahead-of-time compilation.
Example
Per the JEP:
Developers who wish to use the Graal compiler for either AOT or JIT compilation can use GraalVM
This is easily done by setting up our Gradle build to use it, which will give us the task gradlew nativeCompile
which will build an executable (which itself leverages the native-image
tool from the distribution).
build.gradle.kts
:
plugins {
...
id("org.graalvm.buildtools.native") version "0.10.2"
}
repositories {
...
gradlePluginPortal()
}
// Allow GraalVM native AOT compilation
graalvmNative {
binaries {
all {
javaLauncher = javaToolchains.launcherFor {
// NB: On MacOS ARM ARCH (Silicon) the native-image implementation is not available
// for the versions of GRAAL_VM Community edition - selecting Oracle
languageVersion = JavaLanguageVersion.of(22)
vendor = JvmVendorSpec.matching("Oracle")
// languageVersion = JavaLanguageVersion.of(17)
// vendor = JvmVendorSpec.GRAAL_VM
}
}
}
}
Executed:
❯ ./gradlew nativeCompile
BUILD SUCCESSFUL in 425ms
4 actionable tasks: 4 up-to-date
❯ ls ./app/build/native/nativeCompile/app
./app/build/native/nativeCompile/app
Context-Specific Deserialization Filters
Now this isn’t the most sexy sounding feature but if you’ve lost vacation dealing with zero-day vulnerabilities and exploits then you will at least understand the motivation which is to provide a
level of protection against deserialization vulnerabilities. This feature is used by implementing an ObjectInputFilter
Example
Even writing the line implements Serializable
feels like a relic of the past since any sane engineer would be using something like json, avro, protocol-buffers, or some other object serialization flavor of their choice to serialize or persist data, but this is over-engineering java after all so let’s go!
First of all we make all the game objects Serializable
and introduce a new class that can load the game state (that is now persisted after each turn) at startup.
public class GamePersistence {
public void saveTo(File gameFile, Game game) throws IOException {
try (
FileOutputStream os = new FileOutputStream(gameFile);
ObjectOutputStream o = new ObjectOutputStream(os)
) {
o.writeObject(game);
}
System.out.println("[Saved to " + gameFile + "]");
}
public Game loadFrom(File gameFile) throws IOException, ClassNotFoundException {
try (
FileInputStream is = new FileInputStream(gameFile);
ObjectInputStream o = new ObjectInputStream(is)
) {
o.setObjectInputFilter(new GamePersistenceFilter());
return Game.class.cast(o.readObject());
}
}
private static class GamePersistenceFilter implements ObjectInputFilter {
// OVER-ENGINEER Reject any loaded classes games > 1000 object references
private static final long MAX_REFERENCES = 1000;
public Status checkInput(FilterInfo filterInfo) {
if (filterInfo.references() > MAX_REFERENCES) {
return Status.REJECTED;
}
if (null != filterInfo.serialClass()) {
if (LegacyPlayer.class.equals(filterInfo.serialClass())) {
return Status.REJECTED;
}
return Status.ALLOWED;
}
return Status.UNDECIDED;
}
}
}
So when we deserialize i.e. convert from a file via an ObjectInputStream
into our Game
object we can choose to reject deserialization any game objects with a contrived number (1000
) of object references or any that use a LegacyPlayer
(added for this purpose to our sealed Player
hierarchy) which we no longer support.
Introduction to JDK 18
Java 18 introduces several notable features14 aimed at enhancing developer productivity, performance, robustness and modernity. Among these are the introduction of UTF-8 as the default charset, which improves consistency across different environments, a new Simple Web Server was added for testing and development purposes. The deprecation of the finalization mechanism marked a significant step towards improving resource management in Java which had multiple critical flaws15. Additionally, Java 18 included enhancements in the foreign function and memory API, allowing for safer and more efficient interactions with non-Java code and memory.
Also of note are some changes to internals, notably core reflection now using java.lang.invoke
method handles, the reflection API remains the same but there are some marginal performance differences16.
Features
UTF-8 by Default
In prior versions of Java the default charset could be different on different operating systems17 and was determined on JVM startup, which could lead to files becoming corrupted on read. There was also a lack of consistency about which character set would be used (the default) or specifically the most common on the world-wide-web: UTF-8
. In general, the added benefit is less verbosity with character reading/writing and the ability to use default method references (::
) rather than overloads. Python made a similar (more painful) transition when it went from v2 to v3.
The potential issue is that running with JDK 18+ without some flags may lead to files saved with a prior JDK under a different default character set not being loaded properly. In this case the JVM can be run with -Dfile.enconding=COMPAT
. To retrieve and use the native encoding of the system the property System.getProperty("native.encoding")
can be submitted to Charset::forName
.
Simple Web Server
This is a simple command-line tool jwebserver
which starts up a minimal web server which serves static files18. If used via the API it can also be enhanced to use custom handlers.
As a long-time engineer this is great - especially when accessing a server/box to retrieve logs/files is a pain in the neck. The Java developer who wanted to do this previously would have to dip into their Python bag of tricks e.g. python -m SimpleHttpServer 9090 (python 2)
or python -m http.server 9090 (python 3)
to open a file server at port 9090. Now this is possible with just Java tools. (In fact under the hood it does something similar: java -m jdk.httpserver
)
Example
Our tic-tac-toe game persists our game state to a number of files in a specific directory, if we wanted to pull them locally for inspection we could simply start a web server on the host in that directory and access it locally or via curl
.
E.g. jwebserver -p 9090
:
❯ cd /var/folders/jx/4clcmgkx18775h6rpcs7vnyw0000gn/T/d0ec9336-5be8-4ff3-a567-f8b00b02db3f13158585413888981846/
❯ ls -l
.rw-r--r-- briancorbin staff 12 KB Tue Jul 16 20:56:17 2024 d0ec9336-5be8-4ff3-a567-f8b00b02db3f.1.game
.rw-r--r-- briancorbin staff 12 KB Tue Jul 16 20:56:17 2024 d0ec9336-5be8-4ff3-a567-f8b00b02db3f.2.game
.rw-r--r-- briancorbin staff 12 KB Tue Jul 16 20:56:20 2024 d0ec9336-5be8-4ff3-a567-f8b00b02db3f.3.game
.rw-r--r-- briancorbin staff 12 KB Tue Jul 16 20:56:20 2024 d0ec9336-5be8-4ff3-a567-f8b00b02db3f.4.game
.rw-r--r-- briancorbin staff 12 KB Tue Jul 16 20:56:23 2024 d0ec9336-5be8-4ff3-a567-f8b00b02db3f.5.game
.rw-r--r-- briancorbin staff 12 KB Tue Jul 16 20:56:23 2024 d0ec9336-5be8-4ff3-a567-f8b00b02db3f.6.game
.rw-r--r-- briancorbin staff 12 KB Tue Jul 16 20:56:27 2024 d0ec9336-5be8-4ff3-a567-f8b00b02db3f.7.game
.rw-r--r-- briancorbin staff 12 KB Tue Jul 16 20:56:27 2024 d0ec9336-5be8-4ff3-a567-f8b00b02db3f.8.game
❯ jwebserver -p 9090
Binding to loopback by default. For all interfaces use "-b 0.0.0.0" or "-b ::".
Serving /private/var/folders/jx/4clcmgkx18775h6rpcs7vnyw0000gn/T/d0ec9336-5be8-4ff3-a567-f8b00b02db3f13158585413888981846 and subdirectories on 127.0.0.1 port 9090
URL http://127.0.0.1:9090/
127.0.0.1 - - [16/Jul/2024:22:29:31 -0400] "GET / HTTP/1.1" 200 -
127.0.0.1 - - [16/Jul/2024:22:29:44 -0400] "GET /d0ec9336-5be8-4ff3-a567-f8b00b02db3f.4.game HTTP/1.1" 200 -
Directory listing at http:///localhost:9090
:
Code Snippets in Java API Documentation
Including code examples in API documentation has generally been a bit of a pain in the past, especially so if those are more complex code examples. This introduces the ability to add both inline and external code snippets as examples in Javadocs, as well as highlight specific regions and substrings of those snippets.
Example
Our tic-tac-toe game previously had almost no documentation because I knew this was coming. I kid, it had no documentation because of lazy developer syndrome: Although, in practice and in my defense, IRL I generally tend to at least document interfaces and entry points. Seriously though, this is the type of work that AI is for!
Adding in some documentation including the tag @snippet
which is throughly documented in JEP 41319 will give nice snippets in generated java docs - which will please the library creators and their clients.
Player.java
:
/**
* Tic-tac-toe player interface for all players
* {@snippet :
* // Create a human player
* Player player = new HumanPlayer("X"); // @highlight region="player" substring="player"
*
* // Choose the next valid move on the game board
* int validBoardLocation = player.nextMove(gameBoard); // @end
* }
*/
public sealed interface Player permits HumanPlayer, BotPlayer, LegacyPlayer {
String getPlayerMarker();
int nextMove(GameBoard board);
}
Which looks like the below in the JavaDocs:
Internet-Address Resolution SPI
Previously, if you wanted to do an internet address lookup using the standard InetAddress
static functions then you were stuck waiting on a blocking call to the operating system to use the local DNS and internet lookup call. This new feature20 allows us to specify / override the default with our own setup and configuration by using our own service provider and implementation. This is great for testing.
Example
Given we didn’t really have a use for InetAddress
in the current implementation I contrived and over-engineered one - printing a user agent-style string at the start of the game identifying both players:
Welcome to Tic-Tac-Toe!
- TicTacToeClient[Human]/1.0 'X' (IP: 127.0.0.1; Host: corbinm1mac.local; Java: 18.0.2.1; OS: Mac OS X 14.5)
- TicTacToeClient[Bot]/1.0 'O' (IP: 127.0.0.1; Host: corbinm1mac.local; Java: 18.0.2.1; OS: Mac OS X 14.5)
This leverages InetAddress
functions as follows:
InetAddress localHost = InetAddress.getLocalHost();
String ipAddress = localHost.getHostAddress();
String hostName = localHost.getHostName();
return String.format("TicTacToeClient[%s]/1.0 '%s' (IP: %s; Host: %s; Java: %s; OS: %s %s)", playerToType(player), player.getPlayerMarker(), ipAddress, hostName, javaVersion, osName, osVersion);
The SPI (service provider interface) comes in handy to override the default behavior via the implementation class InetAddressResolver
and the factory class InetAddressResolverProvider
, which is registered in META-INF/services
as a java.net.spi.InetAddressResolverProvider
. The implementation overrides the default address resolution resolving every ip to 127.0.0.1
and every address to www.example.org
masking the true ones:
ExampleOrgInetAddressResolver
:
public class ExampleOrgInetAddressResolver implements InetAddressResolver {
@Override
public Stream<InetAddress> lookupByName(String host, LookupPolicy lookupPolicy)
throws UnknownHostException {
return Stream.of(InetAddress.getByAddress(new byte[] { 127, 0, 0, 1 }));
}
@Override
public String lookupByAddress(byte[] addr) {
return "www.example.org";
}
}
ExampleOrgInetAddressResolverProvider
:
public class ExampleOrgInetAddressResolverProvider extends InetAddressResolverProvider {
@Override
public InetAddressResolver get(Configuration configuration) {
return new ExampleOrgInetAddressResolver();
}
@Override
public String name() {
return "Example.org Inet Address Resolver Provider";
}
}
This results in:
Welcome to Tic-Tac-Toe!
- TicTacToeClient[Human]/1.0 'X' (IP: 127.0.0.1; Host: www.example.org; Java: 18.0.2.1; OS: Mac OS X 14.5)
- TicTacToeClient[Bot]/1.0 'O' (IP: 127.0.0.1; Host: www.example.org; Java: 18.0.2.1; OS: Mac OS X 14.5)
Deprecation of Finalization for Removal
The Java finally
clause was never a catch all for ensuring resource cleanup for everything you might be holding onto after you were done with it. If you had multiple resources to clean up there was still a chance it wouldn’t happen properly or in a timely way or there would be some error/exception in between resource closing calls leaving one or more unclosed. So we’d develop a finalize
method on an object but unfortunately without any real control over when it would be called.
Per the JEP15 :
Maintainers of libraries and applications that rely upon finalization should consider migrating to other resource management techniques such as the try-with-resources statement and cleaners.
Example
The deprecation of finalization calls for the use of existing features like try-with-resources which will auto close AutoCloseable
classes (which we already use in the tic-tac-toe codebase):
public void saveTo(File gameFile, Game game) throws IOException {
try (
FileOutputStream os = new FileOutputStream(gameFile);
ObjectOutputStream o = new ObjectOutputStream(os)
) {
o.writeObject(game);
}
System.out.println("[Saved to " + gameFile + "]");
}
In the above, the FileOutputStream
and ObjectOutputStream
are automatically closed when they go out of context without any explicit calls to close()
.
More, interesting is using Cleaner
. This sits atop of java phantom references which allow you to take action just before an object is garbage collected. At present in the HumanPlayer
character we’re holding a transient field to accept user input private transient Scanner io = new Scanner(System.in)
. The IDE doesn’t love that we never close it though:
Resource leak: 'io' is never closed
So we resolved that by tweaking how the app runs making the Game
and the HumanPlayer
implement AutoCloseable
and running the game in a try-with-resources block to deterministically ensure the resources are cleaned up.
public void run() throws Exception {
try (var game = new Game()) {
game.play();
};
}
HumanPlayer.java
snippet:
private static final Cleaner cleaner = Cleaner.create();
private final transient Cleaner.Cleanable cleanable = cleaner
.register(this, () -> {
if (io != null) {
io.close();
io = null;
}
});
@Override
public void close() throws Exception {
cleanable.clean();
}
So, now we are really sure io
gets cleaned up both with the explicit use of try-with-resources and should someone happen to forget to use that or the close()
method explicitly then our cleanup will occur for sure just before garbage collection. It would, of course, have more value if we were holding onto a native/foreign resource outside of the JVM memory model which is typically what we would have if we were previously using finalize()
.
Introduction to JDK 19
Java 19 was not a particularly exciting release21 (unless you include incubating/preview features). So instead I asked an AI to come up with a joke about the lack of new features instead. It was actually pretty good:
Quote
A Java developer walks into a bar and orders a JDK 19. The bartender hands them a glass with just a few drops in it.
”What’s this?” asks the developer. “I ordered a full JDK 19!”
The bartender replies, “Sorry, most of it is still in preview. But don’t worry, it’s a RISC-V you’re willing to take!”
As an enterprise guy, it’s not a “risk” I’m going to take on new features just yet (and I’m already using a RISC-compatible AARCH JDK variant), so stay tuned for JDK 21 where most of the quite exciting preview/incubating features get finalized.
Introduction to JDK 20
Ah, Java 2022… the release full of, um, nothing to see (yet). I’m starting to see now that the primary purpose is AI is not to take our jobs or to write our code. It’s to simply tell a few jokes to keep us sane:
Quote
A Java developer excitedly upgrades to JDK 20 from JDK 19, hoping for groundbreaking features. They call their colleague and say, “Hey, I just installed JDK 20! Ask me anything it can do!”
The colleague asks, “Okay, what can it do?”
The developer replies, “Anything JDK 19 can do!”
The colleague sighs, “So… not much then?”
The developer chuckles, “Hey, at least it’s a short learning curve!”
Introduction to JDK 21
Visit Road to JDK 25, JDK 21 for what’s new!
JDK 21. After a few barren releases now we ride! Features galore and long-term support. This is where most of us should be sat in enterprise before we reach JDK 25. Features include Pattern Matching for switch, which allows for more complex and readable data handling; Record Patterns, enabling concise matching against record values; Sequenced collections, and a key encapsulation mechanism API. Additionally, Virtual Threads provide a lightweight and scalable approach to concurrency, making it easier to write concurrent applications.
Quote
In the wasteland of the Java apocalypse, a grizzled developer revs up his rusty JDK 19 machine, sputtering across the barren landscape. Suddenly, a chrome-plated behemoth roars past him, leaving him in a cloud of dust.
”What in the null pointer exception is that?” he cries.
A voice booms from the passing juggernaut: “Witness JDK 21, shiny and chrome! We ride eternal to Valhalla, all features and glory!”
The old dev gapes as he watches the JDK 21 war rig disappear over the horizon, trailing streams of virtual threads and pattern matching switch expressions.
He mutters to himself, “So that’s where all the features went. JDK 19 and 20 were just… guzzoline for this beast!”
Introduction to JDK 22
Visit Road to JDK 25, JDK 22 for what’s new!
JDK 22 keeps up the momentum gained in JDK 21. Unnamed Patterns and Variables, helping to ignore unneeded parts of data structures for cleaner code (Scala, Python had this for ages), the ability to launch multi-file source code programs, and finally delivering an interoperability API with the foreign function and memory API to replace JNI as the defacto way to perform interop, that allows us to interoperate with code and data outside of the runtime and heap.
Quote
In the aftermath of the JDK 21’s triumphant passage, the desolate expanse still echoes with whispers of innovation. Our weathered developer, now aboard his patched-up JDK buggy, surveys the horizon for signs of the next marvel.
“By the garbage collector’s mercy, what sorcery is this?” he gasps.
A spectral voice resonates from the chariot’s core: “Behold JDK 22, forged in the crucible of progress! It storms ahead, unyielding and unstoppable!”
The veteran dev stands in awe, watching the JDK 22 phantom streak through the skies, leaving a trail of high-performance data processing and seamless native integration.
He murmurs, “The journey to Valhalla continues… JDK 21 was a war rig, but this, this is the streamlined juggernaut of our future.”
Introduction to JDK 23
Visit Road to JDK 25, JDK 23 for what’s new!
JDK 23 makes perhaps more news for what didn’t make the cut as for what did. We get improved documentation with markdown support, ZGC becomes generational by default, and the loved and loathed internal API sun.misc.Unsafe
(that we shouldn’t have been using in the first place) heads for the exit.
However, anyone who was hoping for the finalization of String Templates - that combine literal text with embedded expressions to safely, easily, and dynamically construct strings containing values - would be disappointed as it was yoinked from the JDK after two previews[^55]. It’s not even available with --enable-preview
in JDK 23. This proves my point about features not being ready for professional production use until they exit preview; by all means test them out to help the community and process but JEP 12 (which announced the preview process)[^56] always left provision for features in preview to be removed if they didn’t meet community standards and became a permanent fixture in the JDK.
Quote
In the aftermath of JDK 22’s blazing sprint, the road ahead seemed ripe with promise. Our seasoned developer, perched atop his well-worn JDK steed, squints into the distance, sensing the subtle hum of JDK 23 on the horizon.
“By the threads of concurrency, what might lies within?” he muses.
A whisper floats through the air, carried by the winds of optimization: “JDK 23 rides on the wings of speed! Its garbage collectors, honed and sharpened, dance through memory with grace unmatched.”
The developer feels the surge beneath him, the heartbeat of a machine that knows no pause. “Performance,” he breathes, “is no longer a goal, but a state of being.”
Yet, as he peers closer, he notices a hitch, a sudden halt. The promise of string templates, once so bright, now fades into the mist, halted in its tracks before it could shine.
He nods, “The road to perfection is a winding one, but we ride it well. For now, the race continues… without the song of strings.”
Algorithmic Interlude
Visit Over-Engineering Tic-Tac-Toe — An Algorithmic Interlude for a deep dive!
So far our primary task has been to progressively over-engineer tic-tac-toe (available for your pleasure at the overengineering-tictactoe GitHub repository: here) focused primarily on using finalized features from Java Enhancement Proposals).
In this article, though, we’ll take a break — or rather, a massive side quest from JDK enhancements to zoom in improve upon and explore the primary algorithms used in the game and look at a few used in game theory in general. It’s an interesting (even if a bit niche) diversion but worth the time especially with the rather long wait to JDK 24.
Introduction to JDK 24
Visit Road to JDK 25, JDK 24 for what’s new!
JDK 24 started ramping down in December 2024, with no less than…well, 24 JEP features in tow; Jack Bauer would be proud. While many of those features are still in preview, we’re left with more than in this JDK to work with. So much so that I’ve been even more selective this time around!
In a release with more candidate features than even JDK 11, we are left with some necessary refinements — enhancements to virtual threads which were first delivered in JDK 21 which now avoid unnecessary pinning to platform threads[, better streams support, improved startup times and runtime efficiency for modules, as well as memory management enhancements come to the JDK this time around. The security manager is now permanently disabled and 32-bit support has either been removed completely (Windows) or is on its way out (Linux). Integrity by default and memory safety are also further emphasized and encouraged as warnings are issued when using JNI and sun.misc.Unsafe
memory access methods.
Quote
As the dust of JDK 23 settles, our seasoned developer steadies his gaze toward the horizon. There, a new storm brews — JDK 24, its silhouette sharp and purposeful.
“What power does this beast hold?” he whispers, the winds carrying his words forward.
A resonant voice answers: “Behold, JDK 24! Threads woven tighter, its fibers stronger. Gather! For streams, like an oasis, glisten and shine.”
He feels the hum beneath him, the surge of refined connection, the pulse of precision in every line of code.
But amidst the spectacle, a fleeting pang — a feature teased but not delivered. He smirks, weathered and wise. “Not every promise blooms, but what thrives here is more than enough.”
As the phantom of JDK 24 streaks ahead, leaving trails of blazing performance and connectivity, he tightens his grip. “The road is relentless, but so are we. Valhalla awaits.”
Introduction to JDK 25
The journey will continue to JDK 25…
Disclaimer:
The views and opinions expressed in this blog are based on my personal experiences and knowledge acquired throughout my career. They do not necessarily reflect the views of or experiences at my current or past employers
Footnotes
-
“TIOBE Index,” TIOBE. Available: https://www.tiobe.com/tiobe-index/. [Accessed: Jul. 16, 2024] ↩
-
When in Rome (I currently live in the US) I speak as the Romans do but I grew up in the UK calling this “Noughts and crosses”! ↩
-
SDKMAN! “JDK Distributions - SDKMAN! The Software Development Kit Manager.” Accessed July 9, 2024. https://sdkman.io/jdks. ↩
-
“JDK 17.” Available: https://openjdk.org/projects/jdk/17/. [Accessed: Jul. 09, 2024] ↩
-
“JEP 398: Deprecate the Applet API for Removal.” Accessed July 10, 2024. https://openjdk.org/jeps/398. ↩
-
“JEP 411: Deprecate the Security Manager for Removal.” Accessed July 10, 2024. https://openjdk.org/jeps/411. ↩
-
“CWE - 2020 CWE Top 25 Most Dangerous Software Weaknesses.” Accessed July 10, 2024. https://cwe.mitre.org/top25/archive/2020/2020_cwe_top25.html. ↩
-
“JEP 385: Deprecate RMI Activation for Removal.” Available: https://openjdk.org/jeps/385. [Accessed: Jul. 16, 2024] ↩
-
“JEP 409: Sealed Classes.” Accessed July 10, 2024. https://openjdk.org/jeps/409. ↩
-
“Don’t Wanna Be a Player.” In Wikipedia, January 25, 2024. https://en.wikipedia.org/w/index.php?title=Don%27t_Wanna_Be_a_Player&oldid=1198753099. ↩
-
Bloch, Joshua. Effective java. Addison-Wesley Professional, 2017. ↩
-
Meyer, Bertrand. Object-oriented software construction. Vol. 2. Englewood Cliffs: Prentice hall, 1997. ↩
-
“JEP 410: Remove the Experimental AOT and JIT Compiler.” Available: https://openjdk.org/jeps/410. [Accessed: Jul. 16, 2024] ↩
-
“JDK 18.” Available: https://openjdk.org/projects/jdk/18/. [Accessed: Jul. 09, 2024] ↩
-
“JEP 421: Deprecate Finalization for Removal.” Available: https://openjdk.org/jeps/421. [Accessed: Jul. 17, 2024] ↩ ↩2
-
“JEP 416: Reimplement Core Reflection with Method Handles.” Available: https://openjdk.org/jeps/416. [Accessed: Jul. 16, 2024] ↩
-
“JEP 400: UTF-8 by Default.” Available: https://openjdk.org/jeps/400. [Accessed: Jul. 16, 2024] ↩
-
“JEP 408: Simple Web Server.” Available: https://openjdk.org/jeps/408. [Accessed: Jul. 17, 2024] ↩
-
“JEP 413: Code Snippets in Java API Documentation.” Available: https://openjdk.org/jeps/413. [Accessed: Jul. 16, 2024] ↩
-
“JEP 418: Internet-Address Resolution SPI.” Available: https://openjdk.org/jeps/418. [Accessed: Jul. 17, 2024] ↩
-
JDK 19.” Available: https://openjdk.org/projects/jdk/19/. [Accessed: Jul. 09, 2024] ↩
-
“JDK 20.” Available: https://openjdk.org/projects/jdk/20/. [Accessed: Jul. 09, 2024] ↩