Looking at Java 21: The Little Things
There‘s more to a release than only the big-picture features
Besides the “big” features that often aggregate under well-known project names, like “Amber”, “Loom”, or “Panama”, there are many little things in every release that are easy to miss. These API and tool improvements might not be as visible as other features, as they’re not represented by a JEP. That doesn’t mean we don’t need to know about them.
Let’s take a look at some of the “other” changes that Java 21 gave us!
Java’s new String
templates might have been the show-stealer of this release in regards to String
, but there are a few more additions the String
type and adjacent ones.
String#indexOf
We already had 4 different indexOf
methods available to find the index of an int
or String
argument, starting at the beginning or a given index.
Java 21 added another variant for both finding either an int
or String
by providing an end-index in addition to a beginning one:
int indexOf(int ch,
int beginIndex,
int endIndex)
int indexOf(String str,
int beginIndex,
int endIndex)
Be aware that the beginIndex
is inclusive, but the endIndex
is exclusive. In my opinion, they should’ve named them accordingly, as they did with IntStream.rangeClosed
for example.
String#splitWithDelimiters
The two split
methods got a new acquaintance that works a little differently, hence the more expressive name:
String[] splitWithDelimiters(String regex,
int limit)
The splitting itself is like you would expect it from knowing split(String, int)
but the returned String[]
contains the split parts AND the delimiters:
var str = "foo:::bar::hello:world";
var regex = ":+"; // at least one colon
var result = str.splitWithDelimiters(regex, -1);
// => String[7] { "foo", ":::", "bar", "::", "hello", ":", "world" }
As you can see in the actual source code and the API note in the documentation, besides from an optimization for one or two character non-regexes, the call is equivalent to Pattern.compile(regex).splitWithDelimiters(str, limit)
StringBuilder and StringBuffer
Both types received new convenience methods called repeat
for both CharSequence
and int
arguments:
StringBuilder repeat(CharSequence cs,
int count)
StringBuilder repeat(int codePoint,
int count)
StringBuffer repeat(CharSequence cs,
int count)
StringBuffer repeat(int codePoint,
int count)
They all do what it says on the carton:
var santaBuilder = new StringBuilder();
santaBuilder.repeat("Ho! ", 3);
var str = santaBuilder.toString()
// => "Ho! Ho! Ho! "
Identifying Emojis
Over the years, Emojis gained more and more properties, like different color shades and combined presentations. To make identification easier, the Character
type gained 6 new static
methods:
static boolean isEmoji(int codePoint)
static boolean isEmojiPresentation(int codePoint)
static boolean isEmojiModifier(int codePoint)
static boolean isEmojiModifierBase(int codePoint)
static boolean isEmojiComponent(int codePoint)
static boolean isExtendedPictographic(int codePoint)
Complementing these identification methods, new predefined character groups are available in patterns, too:
\p{IsEmoji}
\p{IsEmoji_Presentation}
\p{IsEmoji_Modifier}
\p{IsEmoji_Modifier_Base}
\p{IsEmoji_Component}
What these properties actually mean is defined in the “Unicode Technical Standard #51.
Math-related Additions
Clamping, a common use case, was simplified by adding 4 static
method clamp
for number primitives to ensure a value
is in between or equal min
and max
:
static int clamp(long value,
int min,
int max)
static long clamp(long value,
long min,
long max)
static float clamp(float value,
float min,
float max)
static double clamp(double value,
double min,
double max)
The first method returns an int
to do double duty, providing a way to clamp long
and a (casted) int
into an int
-based range.
All methods are also available on the StrictMath
type.
HttpClient is an AutoClosable (and more)
The java.net.http.HttpClient
now implements AutoClosable
, making it usable in a try-with-resources
block.
There are also 5 new related methods:
// Initiates an orderly shutdown by completing previously submitted
// request but not accepting new ones.
void close()
// Initiates a graceful shutdown and returns immediately
void shutdown()
// Initiates an immediate shutdown and tries to interrupt any active
// operation. Returns immediately, too.
void shutdownNow()
// Waits for the client to terminate for the given duration.
// Returns true if the client was terminated in time.
boolean awaitTermination(Duration duration)
// Checks if a client is terminated.
boolean isTerminated()
Instances obtained via HttpClient.newHttpClient()
or HttpClient.newBuilder()
provide best-effort implementations of these methods.
To learn more about them, check out the “Implementation Note” in the official documentation.
Improved Sleep Quality
Thread.sleep(long millis, int nanos)
can now perform sub-millisecond sleeps on POSIX platforms. Before, non-zero arguments for nanos
were rounded up to a full millisecond before.
Be aware that the actual precision still depends on the underlying system!
JDK Tool Access in JShell
The JShell is one of my favorite tool added to the JDK in version 9, as it provides a quick and easy way to check out code snippets and verify behavior.
To set it up to your particular needs, you can use “start-up scripts”. Before the release of Java 21, there were 3 start-up scripts available:
DEFAULT
: loaded if no start-up script is specified.JAVASE
: imports a lot more packages.PRINTING
: like default, but addsprint
convenience methods.
Now, there’s a new one available: TOOLING
This start-up script introduces various Java tools as methods right into JShell:
void jar(String... args)
void javac(String... args)args)
void javadoc(String... args)
void javap(String... args)
void jdeps(String... args)
void jlink(String... args)
void jmod(String... args)
void jpackage(String... args)
The also available method tools()
lists all available Java tools.
For example, to introspect the Bytecode of a type, you can now do:
jshell> record Point(int x, int y) { }
jshell> javap(Point.class)
Classfile /tmp/TOOLING-11410713526801554514.class
Last modified Oct 3, 2023; size 1281 bytes
SHA-256 checksum 6f3f6c989dacd62919ce9330c7e1cc9a9511402d044198747d9a35c66a1160f4
Compiled from "$JShell$22.java"
public final class REPL.$JShell$22$Point extends java.lang.Record
minor version: 0
major version: 65
flags: (0x0031) ACC_PUBLIC, ACC_FINAL, ACC_SUPER
this_class: #8 // REPL/$JShell$22$Point
super_class: #2 // java/lang/Record
interfaces: 0, fields: 2, methods: 6, attributes: 5
Constant pool:
#1 = Methodref #2.#3 // java/lang/Record."<init>":()V
#2 = Class #4 // java/lang/Record
#3 = NameAndType #5:#6 // "<init>":()V
// ... snip ...
Be aware that starting JShell only with TOOLING
will lack the common default imports, so I suggest always to load DEFAULT
, too:
jshell --start DEFAULT TOOLING
Conclusion
These were just a few of the many changes flying under the radar as they don’t have a JEP attached to them. Nevertheless, it’s always a good idea to check out the “little things”, too, as they can affect such basic types like String
and might contain a hidden gem.
To check out all changes of Java 21, regardless of whether it is a JEP or “just” a ticket, see the official release notes.
If you’re interested in a more technical comparison, I recommend the Java Version Almanac, which compares the API of different versions to highlight all changes, additions, and removals.
Interested in using functional concepts and techniques in your Java code? Check out my book “A Functional Approach to Java”!
Resources
Looking at Java 21 Articles
- Introduction
- String Templates (JEP 430)
- Simple Main Methods and Unnamed Classes (JEP 445)
- Sequenced Collections (JEP 431)
- Scoped Values (JEP 446)
- Switch Pattern Matching (JEP 441)
- Feature Deprecations (JEP 449, 451)
- Record Pattern Matching (JEP 440)
- Generational ZGC (JEP 439)
- Virtual Threads (JEP 444)