// option A
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
// option B
import java.util.*;
this session focuses on Java 25
this is a showcase, not a tutorial
โ go to youtube.com/@java for more
slides at slides.nipafx.dev/java-x
(hit "?" to get navigation help)
ask questions any time
Most final features in Java 25:
module imports
simplified main
flexible constructor bodies
scoped values
AOT method profiling & easier CLI
JFR improvements
compact object headers
Most final features in Java 24:
stream gatherers
class-file API
less virtual thread pinning
AOT class loading & linking
Final features in Java 23:
Markdown in JavaDoc
generational ZGC by default
Most final features in Java 22:
unnamed patterns
FFM API
multi-source-file programs
Current preview/incubating/experimental features:
primitive patterns
string templates
vector API
structured concurrency
stable values
PEM encoding
JFR CPU-time profiling
Final in Java 25 |
Module Imports |
Simplified Main |
Flexible Constructor Bodies |
Ahead-of-Time Computation |
Compact Object Headers |
Final in Java 24, 23, 22 |
Preview in Java 25 |
Which one do you prefer?
// option A
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
// option B
import java.util.*;
Upsides:
more succinct
easier to manage manually
Downsides:
less clear
chance of conflicts
arbitrary slice of API
import module $mod;
imports public API of $mod
your code does not need to be in a module
import module $mod;
Imports all public top-level types in:
packages exported by $mod
packages exported by $mod
to your module
(qualified exports)
packages exported by modules that $mod
requires transitively (implied readability)
Upsides:
much more succinct
trivial to manage manually
Downsides:
less detailed
conflicts are more likely
import module java.base;
import module java.desktop;
import module java.sql;
public class Example {
public void example() {
// error: reference to Date is ambiguous
var outdated = new Date(1997, 1, 18);
// error: reference to List is ambiguous
var letters = List.of("I", "J", "N");
}
}
import module java.base;
import module java.desktop;
import module java.sql;
import java.util.Date;
import java.util.List;
public class Example {
public void example() {
var outdated = new Date(1997, 1, 18);
var letters = List.of("I", "J", "N");
}
}
Consider using module imports, when:
you’re already using star imports
you’re writing scripts, experiments, demos, etc.
Compact source files implicitly import java.base:
// complete Main.java - no explicit imports!
void main() {
List<?> dates = Stream
.of(1, 2, 23, 29)
.map(BigDecimal::new)
.map(day -> LocalDate.of(
2024,
RandomGenerator.getDefault()
.nextInt(11) + 1,
day.intValue()))
.toList();
System.out.println(dates);
}
๐ JEP 511: Module Import Declarations
Final in Java 25 |
Module Imports |
Simplified Main |
Flexible Constructor Bodies |
Ahead-of-Time Computation |
Compact Object Headers |
Final in Java 24, 23, 22 |
Preview in Java 25 |
Java is very mature:
refined programming model
detailed toolchain
rich ecosystem
But this can make it hard to learn for new (Java) developers.
Java needs to be approachable:
for kids
for students
for the frontend dev
for ML/AI folks
etc.
Java needs an on-ramp for new (Java) developers!
On-ramp:
simplified main
method and class
single-source-file execution
multi-source-file execution
Remove requirement of:
String[] args
parameter
main
being static
main
being public
the class itself
// smallest viable Main.java
void main() {
// ...
}
Use java.lang.IO
instead of System.{in|out}
:
void print(Object)
& void println(Object)
to write
String readln(String)
to read input
void main() {
var name = IO.readln("Please enter your name: ");
IO.println("Nice to meet you, " + name);
}
Compact source files implicitly import java.base:
// complete Main.java - no explicit imports!
void main() {
List<?> dates = Stream
.of(1, 2, 23, 29)
.map(BigDecimal::new)
.map(day -> LocalDate.of(
2024,
RandomGenerator.getDefault()
.nextInt(11) + 1,
day.intValue()))
.toList();
System.out.println(dates);
}
It’s easy to execute that file:
java Main.java
The program can expand:
MyFirstJava
โโ Main.java
โโ Helper.java
โโ Lib
โโ library.jar
Run with:
java -cp 'Lib/*' Main.java
Natural progression:
start with main()
need arguments? โ add String[] args
need to organize code? โ add methods
need shared state? โ add fields
need more functionality? โ explore JDK APIs
even more? โ explore simple libraries
need more structure? โ split into multiple files
even more โ use visibility & packages & modules
Doesn’t even have to be that order!
Final in Java 25 |
Module Imports |
Simplified Main |
Flexible Constructor Bodies |
Ahead-of-Time Computation |
Compact Object Headers |
Final in Java 24, 23, 22 |
Preview in Java 25 |
With multiple constructors,
it’s good practice to have one constructor that:
checks all arguments
assigns all fields
Other constructors just forward (if possible).
class Name {
protected final String first, last;
Name(String first, String last) {
// [... checks, assignments, etc. ...]
}
Name(String fst, String mid, String lst) {
this(fst + " " + mid, lst);
}
}
With superclasses, chaining is enforced:
class ThreePartName extends Name {
protected final String middle;
ThreePartName(String fst, String mid, String lst) {
// doesn't compile without this call
super(fst, lst);
this.middle = mid;
}
}
But:
superclass should be initialized
before subclass runs any code
โ no code before super(…)
code before this(…)
would
run before super(…)
โ no code before this(…)
So: no statements before super(…)
/ this(…)
!
With:
class A
class B extends A
class C extends B
Effective constructor execution for new C()
:
Object
โ A
โ B
โ C
Name(String first, String last) {
this.first = first;
this.last = last;
IO.println(this);
}
ThreePartName(String fst, String mid, String lst) {
super(fst, lst);
this.middle = mid;
}
new ThreePartName("Jane", "Jennifer", "Doe");
// prints: "Jane null Doe" ๐
Limitation is inconvenient when you want to:
check arguments
prepare arguments
split/share arguments
class Name {
protected final String first;
protected final String last;
Name(String first, String last) {
this.first = first;
this.last = last;
}
Name(String full) {
// does the same work twice
this(full.split(" ")[0], full.split(" ")[1]);
}
}
Workarounds:
duplicate argument validation & field assignment
add an "internal" constructor to forward to
add static factory method
To enforce a uniform construction protocol:
Records require all custom constructors
to (eventually) call the canonical constructor.
โ Further limits options.
What we want to write:
class Name {
// [... fields, constructor as before ...]
Name(String full) {
String[] names = full.split(" ");
this(names[0], names[1]);
}
}
(Analogous for records.)
Flexible constructor bodies allow just that!
Constructor(...) {
// prologue
// call to this(...) or super(...)
// epilogue
}
Prologue:
can’t touch this
except to assign fields
With:
class A
class B extends A
class C extends B
Effective constructor execution for new C()
:
prologue C
โ prologue B
โ prologue A
โ Object
โ
epilogue A
โ epilogue B
โ epilogue C
Great to:
check arguments
prepare arguments
split/share arguments
assign fields
class ThreePartName extends Name {
protected final String middle;
ThreePartName(String fst, String mid, String lst) {
// can't have a middle name without a first name
requireNonNullNonEmpty(fst);
this.middle = mid;
super(fst, lst);
}
}
class ThreePartName extends Name {
protected final String middle;
ThreePartName(String fst, String mid, String lst) {
this.middle = mid;
// shorten first if middle is given
var shortFst = mid.length() == 1
? fst.substring(0, 1)
: fst;
super(shortFst, lst);
}
}
class ThreePartName extends Name {
protected final String middle;
ThreePartName(String full) {
// split "first middle last" on space (once ๐๐พ)
var names = full.split(" ");
this.middle = names[1];
super(names[0], names[2]);
}
}
Project Valhalla ponders null-restricted fields
code must never observe such a field being null
has to work with super()
subclass sets non-null fields before super()
๐ JEP 513: Flexible Constructor Bodies
๐ฅ Valhalla - Where Are We? (Brian Goetz - Aug 2024)
Final in Java 25 |
Module Imports |
Simplified Main |
Flexible Constructor Bodies |
Ahead-of-Time Computation |
Compact Object Headers |
Final in Java 24, 23, 22 |
Preview in Java 25 |
Java has really good peak performance,
but also tends to have:
slow startup time
slow warmup time
Early work by the runtime:
class loading
callsite linkage
constant pool resolution
interpretation
profile gathering
JIT compilation (C1, C2)
Can we do this ahead of time?
But Java is highly dynamic:
class loading
class redefinition
linkage
access control
method dispatch
run-time typing (e.g. casting)
introspection
JIT compilation, decompilation
How to AOT everything?
Project Leyden introduces AOTCache:
observe JVM
capture decisions in AOTCache
(expansion of CDS Archive)
use as "initial state" during future run
fall back to live observation/optimization
if necessary and possible
# training run (โ profile)
$ java -XX:AOTMode=record
-XX:AOTConfiguration=app.aotconf
-cp app.jar com.example.App ...
# assembly phase (profile โ AOTCache)
$ java -XX:AOTMode=create
-XX:AOTConfiguration=app.aotconf
-XX:AOTCache=app.aot
-cp app.jar
# production run (AOTCache โ performance)
$ java -XX:AOTCache=app.aot
-cp app.jar com.example.App ...
Shortcut for most cases:
# training run (โ AOTCache)
$ java -XX:AOTCacheOutput=app.aot
-cp app.jar com.example.App ...
# production run (AOTCache โ performance)
$ java -XX:AOTCache=app.aot
-cp app.jar com.example.App ...
Improve startup time by making the classes of an application instantly available, in a loaded and linked state, when the HotSpot JVM starts.
Spring PetClinic benchmarks:
up to ~40% startup time reduction
AOT cache size of ~130 MB
Improve warmup time by making method-execution profiles from a previous run of an application instantly available, when the HotSpot Java Virtual Machine starts.
Benchmark of a 100_000x loop over a simple stream:
~20% run time reduction
AOT cache size increased by ~2.5%
Limitation so far:
same JDK release / hardware / OS
consistent class path for training and production
consistent module options
limited use of JVMTI agents
Otherwise, AOTCache is ignored.
๐ JEP 483: AOT Class Loading and Linking ใ
๐ JEP 515: AOT Method Profiling ใ
๐ JEP 514: AOT CLI Ergonomics ใ
๐ฅ A Preview of What’s Coming in Project Leyden (Oct 2024)
Final in Java 25 |
Module Imports |
Simplified Main |
Flexible Constructor Bodies |
Ahead-of-Time Computation |
Compact Object Headers |
Final in Java 24, 23, 22 |
Preview in Java 25 |
Runtime needs to track information per object:
garbage collection: forwarding pointers and object ages
type system: object’s class
locking: unlocked, light-weight, or heavy-weight locks
hashing: identity hash code, once computed
This is stored in the object header.
On 64bit hardware:
95-64 .........................HHHHHHH
63-32 HHHHHHHHHHHHHHHHHHHHHHHH.AAAA.TT
31- 0 CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC
A
: GC age
C
: class word (compressed)
H
: hash code
T
: tag bits
This incurs overhead:
objects tend to be small
(many workloads average 256-512 bits)
header size is noticable
(15-30% on such workloads)
โ This is worth optimizing.
The aforementioned subsystems are
complicated and performance-sensitive.
I’ve only told you the easy part of object headers:
locking operations can override upper 62 bits.
Project Lilliput achieved first milestone:
63-32 CCCCCCCCCCCCCCCCCCCCCCHHHHHHHHHH
31- 0 HHHHHHHHHHHHHHHHHHHHHVVVVAAAASTT
A
: GC age
C
: class word
H
: hash code
S
: self-forwarding
T
: tag bits
V
: Valhalla bits
These changes have several downstream effects:
locking operations no longer override
class word is now much smaller and encded differently
GC forwarding operations become more complex
In special cases, these may be detrimental to performance.
In one setting, the SPECjbb2015 benchmark uses 22% less heap space and 8% less CPU time.
In another setting, the number of garbage collections done by SPECjbb2015 is reduced by 15%, with both the G1 and Parallel collectors.
A highly parallel JSON parser benchmark runs in 10% less time.
Add flag:
-XX:+UseCompactObjectHeaders
Report observations to hotspot-dev!
This is vital to:
improve performance, specifically edge cases
decide whether this becomes default/only header encoding
Interaction with other features:
compressed class pointers must be enabled (default)
stack locking must be disabled (default)
What it was:
a system of checks and permissions
intended to safeguard security-relevant
code sections
embodied by SecurityManager
What you need to know:
barely used but maintenance-intensive
disallowed by default since JDK 18
removed in JDK 24
command-line flags cause errors
SecurityManager
still exists
all calls are no-ops
What you need to do:
observe your app with security manager disabled
(java.security.manager=disallow
on JDK 12+)
if used, move away from security manager
You’re probably not using it directly,
but your dependencies may.
Find them with --sun-misc-unsafe-memory-access
:
warn
: to get run-time warnings
debug
: same with more info
deny
: throws exception
Report and help fix!
What it is:
finalize()
methods
a JLS/GC machinery for them
What you need to know:
you can disable with --finalization=disabled
in a future release, disabled
will be the default
in a later release, finalization will be removed
What you need to do:
search for finalize()
in your code and
replace with try
-with-resources or Cleaner
API
search for finalize()
in your dependencies and
help remove them
run your app with --finalization=disabled
and
closely monitor resource behavior (e.g. file handles)
Native code can undermine Java’s integrity.
App owner should opt in knowingly:
use --enable-native-access
to allow
access to restricted JNI/FFM methods
use --illegal-native-access
for other code
Three options for illegal native access:
allow
warn
(default on JDK 24)
deny
In some future release, deny
will become the only mode.
Prepare now by setting --illegal-native-access=deny
.
What it is:
a component that transforms byte code
uses java.lang.instrument
or JVM TI
launches with JVM or attaches later ("dynamic")
What you need to know:
all mechanisms for agents remain intact
nothing changed yet
in the future, dynamic attach will be
disabled by default
enable with -XX:+EnableDynamicAgentLoading
What you need to do:
run your app with -XX:-EnableDynamicAgentLoading
observe closely
investigate necessity of dynamic agents
๐ all the aforementioned JEPs
AOT class loading & linking ใ
less virtual thread pinning ใ
stream gatherers ใ
class-file API ใ
Markdown in JavaDoc ใ
generational ZGC by default ใ
unnamed patterns ใ
FFM API ใ
primitive patterns โ
string templates โ
Final in Java 25 |
Final in Java 24 |
AOT Class Loading & Linking |
Less Virtual Thread Pinning |
Stream Gatherers |
Class-File API |
Final in Java 23 |
Final in Java 22 |
Preview in Java 25 |
A virtual thread:
is a regular Thread
low memory footprint (stack + bytes)
small switching cost
scheduled by the Java runtime
executes on platform thread
waits in memory
(no platform thread blocked)
a pinned VT will block the PT
caused by object monitors,
native calls, class initialization
a captured VT blocks the PT
caused by file I/O
Object monitor implementation:
was bound to OS threads
required deep refactoring
to work with VTs
fix ships with JDK 24
โ No more pinning for synchronized
.
๐ JEP 491: Synchronize Virtual Threads without Pinning
๐ฅ Java 24 Stops Pinning Virtual Threads (Almost) (Nov 2024)
Final in Java 25 |
Final in Java 24 |
AOT Class Loading & Linking |
Less Virtual Thread Pinning |
Stream Gatherers |
Class-File API |
Final in Java 23 |
Final in Java 22 |
Preview in Java 25 |
Streams are great, but some
intermediate operations are missing:
sliding windows
fixed groups
take-while-including
scanning
increasing sequences
etc.
Streams also don’t have all possible terminal operations.
Instead:
generalization for terminal ops โ collectors
a few implementations, e.g. Collectors.toSet()
extension point for them: Stream::collect
Let’s do the same for intermediate ops!
The gatherers API consists of:
generalization for intermediate ops โ gatherers
a few implementations, e.g. Gatherers.scan(โฆ)
extension point for them: Stream::gather
Stream.of("A", "C", "F", "B", "S")
.gather(scan(...))
.forEach(System.out::println);
One required building block:
accepts (state, element, downstream)
has the task to combine state
and element
to update the state
to emit 0+ result(s) to downstream
Behaves transparently:
static <T> Gatherer<T, ?, T> transparent() {
Integrator<Void, T, T> integrator = (_, el, ds)
-> ds.push(el);
return Gatherer.of(integrator);
}
Reimplements Stream::map
:
static <T, R> Gatherer<T, ?, R> map(Function<T, R> f) {
Integrator<Void, T, R> integrator = (_, el, ds) -> {
R newEl = f.apply(el);
return ds.push(newEl);
};
return Gatherer.of(integrator);
}
Three optional building blocks:
creates instance(s) of state
accepts (state, downstream)
emits 0+ element(s) to downstream
combines to states
into one
Create groups of fixed size:
stream input: "A", "C", "F", "B", "S"
output of groups(2)
: ["A", "C"], ["F", "B"], ["S"]
We need:
an initializer to create empty group list
an integrator that emits when group is full
a finisher to emit incomplete group
Supplier<List<T>> initializer = ArrayList::new;
Integrator<List<T>, T, List<T>> integrator =
(list, el, ds) -> {
list.add(el);
if (list.size() < size)
return true;
else {
var group = List.copyOf(list);
list.clear();
return ds.push(group);
}
};
BiConsumer<List<T>, Downstream<List<T>>> finisher =
(list, ds) -> {
var group = List.copyOf(list);
ds.push(group);
};
static <T> Gatherer<T, ?, List<T>> groups(int size) {
Supplier<...> initializer = // ...
Integrator<...> integrator = // ...
BiConsumer<...> finisher = // ...
return Gatherer.ofSequential(
initializer, integrator, finisher);
}
Using our gatherer:
Stream.of("A", "C", "F", "B", "S")
.gather(groups(2))
.forEach(System.out::println);
// [A, C]
// [F, B]
// [S]
๐ JEP 485: Stream Gatherers
๐ฅ Teaching Old Streams New Tricks (Viktor Klang)
Final in Java 25 |
Final in Java 24 |
AOT Class Loading & Linking |
Less Virtual Thread Pinning |
Stream Gatherers |
Class-File API |
Final in Java 23 |
Final in Java 22 |
Preview in Java 25 |
Bytecode is instruction set for JVM:
creating objects and arrays
copying variable values or references
between stack and registers
invoking methods
computing arithmetic operations
etc.
Basic lifecycle:
generated by javac
stored in .class
files
loaded, parsed, verified by class loader
executed by JVM
In real life, much more happens:
generated by frameworks at build time
turned into machine code by JIT compiler
or Graal native image
prefetched by AOTCache
analyzed by jdeps, SpotBugs, etc.
manipulated by agents and libraries
Tooling:
libraries don’t manipulate bytecode themselves
they use a few tools
Big player is ASM
(direct or, e.g., via ByteBuddy or CGLIB).
Updates:
bytecode has a level (e.g. 65 for Java 21)
tools can’t work with a higher level
than they were built for
This can block updates!
E.g. when compiling your code with Java 25 (level 69)…
This is the reason for:
Before updating the JDK,
update all dependencies.
We want to move past that!
An API in Java that allows
analyzing and manipulating bytecode:
stable API in JDK
always up-to-date
When JDK is updated:
it may read new bytecodes
but that’s ok for most use cases
๐ JEP 484: Class-File API
๐ฅ A Classfile API for the JDK (Brian Goetz)
Final in Java 25 |
Final in Java 24 |
Final in Java 23 |
Markdown in JavaDoc |
GenZGC by Default |
Final in Java 22 |
Preview in Java 25 |
Writing simple JavaDoc is great!
Writing more complex documentation…
where does <p>
go?
do we need </p>
?
code snippets/blocks are cumbersome
lists are verbose
tables are terrible
…
I blame HTML!
Markdown is more pleasant to write:
neither <p>
nor </p>
code snippts/blocks are simple
lists are simple
tables are less terrible
embedding HTML is straightforward
Markdown is widely used and known.
Java now allows Markdown JavaDoc:
each line starts with ///
CommonMark 0.30
links to program elements use extended
reference link syntax: [text][element]
JavaDoc tags work as usual
[Introduced in Java 23 — JEP 467]
Wouldn’t this be nice:
/**md
*
* Markdown here...
*
*/
No - reason #1:
/**md
*
* Here's a list:
*
* item #1
* item #1
*
*/
(The leading *
in JavaDoc is optional.)
No - reason #2:
/**md
*
* ```java
* /* a Java inline comment */
* ```
*
*/
(/**
can’t contain */
.)
///
:
no such issues
doesn’t require new Java syntax
(//
already "escapes" parsing)
Inline code with `backticks
`.
Code blocks with fences, e.g.:
```java public void example() { } ```
A language tag is set as a CSS class
for JS-based highlighting in the frontend.
(Add a library with javadoc --add-script …
.)
Use full reference link to add text:
/// - [the _java.base_ module][java.base/]
/// - [the `java.util` package][java.util]
/// - [the `String` class][String]
/// - [the `String#CASE_INSENSITIVE_ORDER` field][String#CASE_INSENSITIVE_ORDER]
/// - [the `String#chars()` method][String#chars()]
Output:
Markdown tables:
better than HTML tables
still uncomfortable to create manually
use something like tablesgenerator.com
Advanced tables:
for features unsupported in Markdown,
create HTML tables
JavaDoc tags work as expected:
can be used in Markdown comments
if they contain text, Markdown syntax works
/// For more information on comments,
/// see {@jls 3.7 Comments}.
///
/// @implSpec this implementation does _nothing_
public void doSomething() { }
๐ JEP 467: Markdown Documentation Comments
Final in Java 25 |
Final in Java 24 |
Final in Java 23 |
Markdown in JavaDoc |
GenZGC by Default |
Final in Java 22 |
Preview in Java 25 |
Compared to other GCs, ZGC:
optimizes for ultra-low pause times
can have higher memory footprint or higher CPU usage
In JDK 21, ZGC became generational.
most objects die young
those who don’t, grow (very) old
GCs can make use of this by tracking
young and old generations.
ZGC didn’t do this, but can do it now.
Generational mode is:
the default on JDK 23
the only mode on JDK 24+
-XX:+UseZGC
(Default GC is still G1.)
๐ JEP 439: Generational ZGC
๐ JEP 474: ZGC: Generational Mode by Default
๐ฅ Generational ZGC and Beyond (Aug 2023)
๐ฅ Java’s Highly Scalable Low-Latency GC: ZGC (Mar 2023)
Final in Java 25 |
Final in Java 24 |
Final in Java 23 |
Final in Java 22 |
Unnamed Patterns |
FFM API |
Preview in Java 25 |
Use _
to mark a (pattern) variable as unused, e.g.:
BiConsumer<String, Double> = (s, _) -> // use `s`
Object obj = // ...
if (obj instanceof User(var name, _))
// use `name`
switch (obj) {
case User _ -> userCount++;
case Admin _ -> adminCount++;
}
That last one is very important!
Features:
scrapes GitHub projects
creates Page
instances:
GitHubIssuePage
GitHubPrPage
ExternalPage
ErrorPage
further processes pages
Features:
display as interactive graph
compute graph properties
categorize pages by topic
analyze mood of interactions
process payment for analyses
etc.
How to implement features?
methods on Page
๐ง
visitor pattern ๐ซ
pattern matching ๐ฅณ
Approach:
make Page
sealed
implement features as methods outside of Page
accept Page
parameters and switch
over it
avoid default
branch for maintainability
Sealed types limit inheritance,
by only allowing specific subtypes.
public sealed interface Page
permits GitHubIssuePage, GitHubPrPage,
ExternalPage, ErrorPage {
// ...
}
public Category categorize(Page page) {
return switch (page) {
case GitHubIssuePage is -> categorizeIssue(is);
case GitHubPrPage pr -> categorizePr(pr);
case ExternalPage ext -> categorizeExternal(ext);
case ErrorPage err -> categorizeError(err);
}
}
Sometimes you have "defaulty" behavior:
public Category categorize(Page page) {
return switch (page) {
// categorize only GitHub pages
case GitHubIssuePage is -> categorizeIssue(is);
case GitHubPrPage pr -> categorizePr(pr);
// return Category.NONE for other page types
}
}
How to handle remaining cases?
Unlike an if
-else
-if
-chain,
a pattern switch
needs to be exhaustive.
Fulfilled by:
a default
branch
explicit branches:
switching over a sealed types
a case
per subtype
Option 1:
public Category categorize(Page page) {
return switch (page) {
case GitHubIssuePage is -> categorizeIssue(is);
case GitHubPrPage pr -> categorizePr(pr);
default -> Category.NONE;
}
}
If GitHubCommitPage
is added:
public Category categorize(Page page) {
return switch (page) {
case GitHubIssuePage is -> categorizeIssue(is);
case GitHubPrPage pr -> categorizePr(pr);
// `GitHubCommitPage` gets no category!
default -> Category.NONE;
}
}
โ Adding a new subtype causes no compile error! โ
Option 2 in Java 21
(without preview features):
public Category categorize(Page page) {
return switch (page) {
case GitHubIssuePage is -> categorizeIssue(is);
case GitHubPrPage pr -> categorizePr(pr);
// duplication ๐ข
case ErrorPage err -> Category.NONE;
case ExternalPage ext -> Category.NONE;
};
}
If GitHubCommitPage
is added:
public Category categorize(Page page) {
// error:
// "the switch expression does not cover
// all possible input values"
return switch (page) {
case GitHubIssuePage is -> categorizeIssue(is);
case GitHubPrPage pr -> categorizePr(pr);
case ErrorPage err -> Category.NONE;
case ExternalPage ext -> Category.NONE;
}
}
โ Adding a new subtype causes a compile error! โ
Would be nice to combine branches:
public Category categorize(Page page) {
return switch (page) {
case GitHubIssuePage is -> categorizeIssue(is);
case GitHubPrPage pr -> categorizePr(pr);
case ErrorPage err, ExternalPage ext
-> Category.NONE;
};
}
Doesn’t make sense.
(Neither err
nor ext
would be in scope.)
Use _
to combine "default branches":
public Category categorize(Page page) {
return switch (page) {
case GitHubIssuePage is -> categorizeIssue(is);
case GitHubPrPage pr -> categorizePr(pr);
case ErrorPage _, ExternalPage _
-> Category.NONE;
};
}
โ Default behavior without default
branch. ๐ฅณ
๐ JEP 456: Unnamed Variables & Patterns
Final in Java 25 |
Final in Java 24 |
Final in Java 23 |
Final in Java 22 |
Unnamed Patterns |
FFM API |
Preview in Java 25 |
Storing data off-heap is tough:
ByteBuffer
is limited (2GB) and inefficient
Unsafe
is… unsafe and not supported
Panama introduces safe and performant API:
control (de)allocation:
Arena
, MemorySegment
, SegmentAllocator
to access/manipulate: MemoryLayout
, VarHandle
// create `Arena` to manage off-heap memory lifetime
try (Arena offHeap = Arena.ofConfined()) {
// [allocate off-heap memory to store pointers]
// [do something with off-heap data]
// [copy data back to heap]
} // off-heap memory is deallocated here
Allocate off-heap memory to store pointers:
String[] javaStrings = { "mouse", "cat", "dog" };
// Arena offHeap = ...
MemorySegment pointers = offHeap.allocateArray(
ValueLayout.ADDRESS, javaStrings.length);
for (int i = 0; i < javaStrings.length; i++) {
// allocate off-heap & store a pointer
MemorySegment cString = offHeap
.allocateUtf8String(javaStrings[i]);
pointers
.setAtIndex(ValueLayout.ADDRESS, i, cString);
}
Copy data back to heap:
// String[] javaStrings = ...
// MemorySegment pointers =
for (int i = 0; i < javaStrings.length; i++) {
MemorySegment cString = pointers
.getAtIndex(ValueLayout.ADDRESS, i);
javaStrings[i] = cString.getUtf8String(0);
}
JNI isn’t ideal:
involves several tedious artifacts (header file, impl, …)
can only interoperate with languages that align
with OS/architecture the JVM was built for
doesn’t reconcile Java/C type systems
Panama introduces streamlined tooling/API
based on method handles:
jextract
: generates method handles from header file
classes to call foreign functions
Linker
, FunctionDescriptor
, SymbolLookup
// find foreign function on the C library path
Linker linker = Linker.nativeLinker();
SymbolLookup stdlib = linker.defaultLookup();
MethodHandle radixSort = linker
.downcallHandle(stdlib.find("radixsort"), ...);
String[] javaStrings = { "mouse", "cat", "dog" };
try (Arena offHeap = Arena.ofConfined()) {
// [move Java strings off heap]
// invoke foreign function
radixSort.invoke(
pointers, javaStrings.length,
MemorySegment.NULL, '\0');
// [copy data back to heap]
}
Java 22 finalizes the FFM API, but there’s more to do:
user-friendly and performant mapping from
native memory to Java records/interfaces
improving jextract and surrounding tooling
And more.
๐ JEP 454: Foreign Function & Memory API
๐ฅ Project Panama - Foreign Function & Memory API (Maurizio Cimadamore)
Final in Java 25 |
Final in Java 24 |
Final in Java 23 |
Final in Java 22 |
Preview in Java 25 |
Primitive Patterns |
In instanceof
and switch
, patterns can:
match against reference types
deconstruct records
nest patterns
ignore parts of a pattern
In switch
:
refine the selection with guarded patterns
That (plus sealed types) are
the pattern matching basics.
This will be:
built up with more features
built out to re-balance the language
The x instanceof Y
operation:
meant: "is x
of type Y
?"
now means: "does x
match the pattern Y
?"
For primitives:
old semantics made no sense
โ no x instanceof $primitive
new semantics can make sense
Example: int number = 0;
Can number
be an instance of byte
?
No, it’s an รฌnt
.
But can its value be a byte
?
Yes!
int x = 0;
if (x instanceof byte b)
System.out.println(b + " in [-128, 127]");
else
System.out.println(x + " not in [-128, 127]");
What’s special about 16_777_217?
Smallest positive int
that float
can’t represent.
int x = 16_777_216;
if (x instanceof float f)
// use `f`
Boolean bool = // ...
var emoji = switch (bool) {
case null -> "";
case true -> "โ
";
case false -> "โ";
}
(Bugged in 23; fixed in 23.0.1 and 24-EA.)
๐ JEP 455: Primitive Types in Patterns, instanceof, and switch
Final in Java 25 |
Final in Java 24 |
Final in Java 23 |
Final in Java 22 |
Preview in Java 25 |
Primitive Patterns |
Composing strings in Java is cumbersome:
String property = "last_name";
String value = "Doe";
// concatenation
String query =
"SELECT * FROM Person p WHERE p."
+ property + " = '" + value + "'";
// formatting
String query =
"SELECT * FROM Person p WHERE p.%s = '%s'"
.formatted(property, value);
Comes with free SQL injection! ๐ณ
Why not?
// (fictional syntax!)
String query =
"SELECT * FROM Person p "
+ "WHERE p.\{property} = '\{value}'";
Also comes with free SQL injection! ๐ณ
SQL injections aren’t the only concern.
These also need validation and sanitization:
HTML/XML
JSON
YAML
…
All follow format-specific rules.
string templates were removed from JDK 23
(not even a preview)
the feature needs a redesign
timing is unknown
๐
In a few slides…
Major achievements:
pave the on-ramp
add essential lower-level APIs
improve tooling and performance
introduce AOT workflow
Get JDK 25 EA at jdk.java.net/25.
all other images are copyrighted