Java 17 Is Here!

Developer Advocate

Java Team at Oracle

Let’s get started!

  • this talk covers Java 17
    and whatever else we have time for

  • this is a showcase, not a tutorial

  • slides at slides.nipafx.dev/java-x
    (hit "?" to get navigation help)

  • they cover Java 9 to 17+
    (without module system)

Lots to talk about!

Language Changes
New and Updated APIs
New JVM Features
Performance Improvements

New Language Features

Pattern Matching in Switch
Sealed Classes ⑰
Records ⑯
Type Pattern Matching ⑯
Text Blocks ⑮
Switch Expressions ⑭
Local-Variable Type Inference ⑩
All The Small Things... ⑨⑯

New Language Features

Pattern Matching in Switch
Sealed Classes ⑰
Records ⑯
Type Pattern Matching ⑯
Text Blocks ⑮
Switch Expressions ⑭
Local-Variable Type Inference ⑩
All The Small Things... ⑨⑯

Standing on three pillars.

Preview Feature

Note:

  • must be enabled with --enable-preview
    (on javac and java).

  • in IntelliJ, set the module’s language level to
    17 (Preview) - …​

  • in Eclipse, go to Compiler Settings
    and check Enable preview features

Patterns in Switch

Patterns and switch evolution
are coming together:

// tired
if (staff instanceof Employee employee)
	employee.paySalary();
else if (staff instanceof Freelancer freelancer)
	freelancer.payBill();

// wired
switch (staff) {
	case Employee emp -> emp.paySalary();
	case Freelancer free -> free.payBill();
	default -> { }
}

Patterns in Switch

When using patterns in switch:

  • switched-over variable becomes target

  • pattern goes after case

//      target
switch (staff) {
	//   |- pattern -|
	case Employee emp -> emp.paySalary();
	//   |- pattern ---|
	case Freelancer free -> free.payBill();
	default -> { }
}

Patterns in Switch

Things to discuss:

  • null handling

  • guarded patterns

  • completeness

Null Handling

Explicit with null label:

switch (staff) {
	case Employee emp -> emp.paySalary();
	case Freelancer free -> free.payBill();
	case null -> { }
	default -> { }
}

Null Handling

Default branch does not handle null
but branches can be merged:

switch (staff) {
	case Employee emp -> emp.paySalary();
	case Freelancer free -> free.payBill();
	case null, default -> { }
}

Null Handling

A pattern is total
if it covers all possible inputs:

Staff staff = //...
switch (staff) {
	case Employee emp -> //...
	case Freelancer free -> //...
	// this is a "total" pattern
	case Staff s -> { }
}

Total patterns include null!

Null Handling

If switching over null,
these branches can handle it:

  • case null

  • total pattern

Whatever comes first.

(But: case null after total pattern is an error.)

Guarded Patterns

Extending patterns with boolean checks:

//  |---------- GUARDED PATTERN -----------|
//              | PATTERN |   | BOOL EXPR -|
if (o instanceof (String s && s.length() > 1))
	return "non-trivial string";

Equivalent:

//  |----- PATTERN -----|    | BOOL EXPR -|
if (o instanceof String s && s.length() > 1)
	return "non-trivial string";

Guarded Patterns

Why?

⇝ Future developments!

//                                    boolean
if (shape instanceof Point(var x, var y) && x > 0)
	// ...

//                         guarded pattern
if (shape instanceof Point(var x && x > 0, var y))
	// ...

🤷🏾‍♂️

Guarded Patterns

Also works in switch!

String result = switch (object) {
	case String s && s.length() > 1 -> "string";
	case String s -> "character";
	default -> "";
}

Completeness

Like switch expressions, pattern switches need to be complete:

// compile error
switch (staff) {
	case Employee emp -> emp.paySalary();
	case Freelancer free -> free.payBill();
}

Or is it?!

Completeness and Sealed Classes

Third pillar comes into play:

sealed interface Staff
	permits Employee, Freelancer { }

switch (staff) {
	case Employee emp -> emp.paySalary();
	case Freelancer free -> free.payBill();
	// no default branch needed!
}

This is big!

Adding Operations

Best way to add functionality:

  1. as methods to interfaces / classes 👍🏾

  2. visitor pattern 😕

Third option: patterns over sealed classes!

  • leaves types untouched

  • simple to branch based
    on types and conditions

  • compile errors for new types

Teasing Apart Data

Pattens also help on boundary.

Consider parsed JSON:

  • mostly subtypes of JsonNode

  • if that’s sealed, switch over:

    • StringJsonNode

    • ArrayJsonNode

    • etc.

Summary

  • merges pattern matching, switch evolution
    and sealed types

  • makes it easy and safe to branch
    based on types and conditions

  • improves null handling

New Language Features

Pattern Matching in Switch
Sealed Classes ⑰
Records ⑯
Type Pattern Matching ⑯
Text Blocks ⑮
Switch Expressions ⑭
Local-Variable Type Inference ⑩
All The Small Things... ⑨⑯

Between final and open classes

Modeling A Domain

Many systems have central abstractions, e.g.:

  • staff/customers

  • delivery mechanisms

  • shapes

Commonly, polymorphism is used
to reuse code and attach functionality.

If many subsystems operate on abstractions,
there’s the risk of feature creep.

Modeling A Domain

Alternatively, subsystems can
implement their own handling.

Challenge is that subtypes
are effectively unknown, e.g.:

  • what subtypes of Staff exist?

  • what subtypes of Shape exist?

OO-solutions are cumbersome.
(e.g. visitor pattern)

Modeling A Closed Domain

In many cases, a type’s variations
are finite and known, e.g.:

  • Employee, Freelancer extend Staff

  • Circle, Rectangle extend Shape

If subsystems rely on that,
their code becomes simpler (instanceof).

But less maintainable?
⇝ Only because compiler can’t help!

Compiler & Inheritance

There’s three options how a class can be extended:

  • by no classes (final class)

  • by package classes
    (package-visible constructor)

  • by all classes (public class)

(For interfaces, there’s no choice at all.)

In all cases:
Implementations are unknown to the compiler.

Enter Sealed Types

With sealed types, we can express
limited extensibility:

  • only specific types can extend sealed type

  • those are known to developer and compiler

Sealed Staff

  • mark class/interface as sealed

  • use permits to list types

sealed interface Staff
	permits Employee, Freelancer { }

final class Employee implements Staff { }

final class Freelancer implements Staff { }

// compile error
final class Consultant implements Staff { }

Handling Sealed Staff

Goal is to combine sealed types,
switch expressions, and type patterns.

But we’re not there yet - for now:

  • sealed classes limit extensibility
    (between final and non-final)

  • prevent extension by users

  • express intention to maintainers

Sealing Details

There are a few details to discuss:

  • for the sealed type

  • for the permitted types

  • for both of those types

Sealed Type Details

Sealed types can extend/inherit as usual:

sealed class Staff
	extends Person
	implements Comparable<Staff>
	permits Employee, Freelancer {

	// ...

}

Permitted Type Details

Permitted types must use exactly one of these modifiers:

  • final for no inheritance

  • sealed for limited inheritance

  • non-sealed for unlimited inheritance

With sealed and non-sealed, a type
can admit further implementations.

Permitted Type Details

sealed interface Staff
	permits Employee, Freelancer { }

non-sealed class Employee implements Staff { }

sealed class Freelancer implements Staff
	permits Consultant { }

final class Consultant extends Freelancer { }

But what about completeness?!
⇝ type pyramid has "complete peak"

Permitted Type Details

Permitted types must directly extend sealed type:

sealed interface Staff
	//                 compile error
	permits Freelancer, Consultant { }

non-sealed class Freelancer implements Staff { }

class Consultant extends Freelancer { }

This keeps type pyramid layered.

Permitting Records

Remember, records are implicitly final.

They make good permitted types.

Neighbours

Permitted types must be "close":

  • same package for non-modular JAR

  • same module for modular JAR

Sealed and each permitted type must be
visible/accesible to one another.

Flat Mates

If all types are in same source file,
permits can be omitted:

public class Employment {

	sealed interface Staff { }

	final class Employee implements Staff { }

	final class Freelancer implements Staff { }

}

Summary

Sealed types make inheritance:

  • more flexible between open and final

  • analyzable to the compiler

Limited inheritance is
one pillar for pattern matching.

New Language Features

Pattern Matching in Switch
Sealed Classes ⑰
Records ⑯
Type Pattern Matching ⑯
Text Blocks ⑮
Switch Expressions ⑭
Local-Variable Type Inference ⑩
All The Small Things... ⑨⑯

Simple classes ~> simple code

Spilling Beans

Typical Java Bean:

public class Range {

	// part I 😀

	private final int low;
	private final int high;

	public Range(int low, int high) {
		this.low = low;
		this.high = high;
	}

}

Spilling Beans

public class Range {

	// part II 🙄

	public int getLow() {
		return low;
	}

	public int getHigh() {
		return high;
	}

}

Spilling Beans

public class Range {

	// part III 🤨

	@Override
	public boolean equals(Object o) {
		if (this == o)
			return true;
		if (o == null || getClass() != o.getClass())
			return false;
		Range range = (Range) o;
		return low == range.low &&
				high == range.high;
	}

}

Spilling Beans

public class Range {

	// part IV 🥴

	@Override
	public int hashCode() {
		return Objects.hash(low, high);
	}

}

Spilling Beans

public class Range {

	// part V 😭

	@Override
	public String toString() {
		return "[" + low + "; " + high + "]";
	}

}

"Java is Verbose"

Range.java is simple:

  • declares type Range

  • declares two components, low and high

Takes 44 lines!

  • verbose

  • room for error

  • unexpressive

Records

//                these are "components"
public record Range(int low, int high) {

	// compiler generates:
	//  * (final) fields
	//  * canonical constructor
	//  * accessors low(), high()
	//  * equals, hashCode, toString

}

Records

The API for a record models the state, the whole state, and nothing but the state.

The deal:

  • give up encapsulation

  • couple API to internal state

  • get API for free

Records

The benefits:

  • no boilerplate for plain "data carriers"

  • no room for error

  • makes Java more expressive

On to the details!

Limited Records

Records are limited classes:

  • no inheritance

    • can’t use extends

    • are final

  • component fields are final

  • no additional fields

Customizable Records

Records can be customized:

  • override constructor

  • add constructors and
    static factory methods

  • override accessors

  • add other methods

  • override Object methods

  • implement interfaces

  • make serializable

Override Constructors

public Range(int low, int high) {
	if (high < low)
		throw new IllegalArgumentException();
	this.low = low;
	this.high = high;
}

Override Constructors

Compact canonical constructor:

// executed before fields are assigned
public Range {
	if (high < low)
		throw new IllegalArgumentException();
}

// arguments can be reassigned
public Range {
	if (high < low)
		high = low;
}

Override Constructors

  • implicit constructor has same visibility as record

  • explicit constructors can’t reduce visibility

  • can’t assign fields in compact form
    (happens automatically after its execution)

Add Constructors

Additional constructors work as usual:

public Range(int high) {
	this(0, high);
}

(Compact canonical constructor can’t delegate.)

Add Static Factories

Additional static factories work as usual:

public static Range open(int low, int high) {
	return new Range(low, high + 1);
}

Can’t reduce constructor visibility, though.

@Deprecated
// use static factory method instead
public Range { }

Override Accessors

Accessors can be overridden:

@Override
public low() {
	return Math.max(0, low);
}

Not a good example!

The API for a record models the state, the whole state, and nothing but the state.

Implement Interfaces

public record Range(int low, int high)
		implements Comparable<Range> {

	@Override
	public int compareTo(Range other) {
		return this.low == other.low
			? this.high - other.high
			: this.low - other.low;
	}

}

Serializable Records

public record Range(int low, int high)
		implements Serializable { }
  • has default serialVersionUID 0

  • uses FileOutputStream and
    FileInputStream as usual

  • deserializaton calls constructor 🙌

  • framework support is growing
    (e.g. Jackson, Apache Johnzon)

Summary

  • use records to replace data carriers

  • it’s not anti-boilerplate pixie dust
    ⇝ use only when "the deal" makes sense

  • beware of limitations

  • beware of class-building facilites

  • observe ecosystem for adoption

New Language Features

Pattern Matching in Switch
Sealed Classes ⑰
Records ⑯
Type Pattern Matching ⑯
Text Blocks ⑮
Switch Expressions ⑭
Local-Variable Type Inference ⑩
All The Small Things... ⑨⑯

Type check and cast in one operation.

Old-school instanceof

instanceof is cumbersome:

public void pay(Staff staff) {
	if (staff instanceof Employee)
		((Employee) staff).paySalary();
	else if (staff instanceof Freelancer)
		((Freelancer) staff).payBill();
}

Three things are happening:

  1. type test

  2. type conversaion

  3. variable declaration (implicit)

Type Pattern Matching

public void pay(Staff staff) {
	if (staff instanceof Employee employee)
		employee.paySalary();
	else if (staff instanceof Freelancer freelancer)
		freelancer.payBill();
}

staff instanceof Employee employee:

  1. does all three things in one operation

  2. employee is scoped to true-branch

What is a Pattern?

A pattern is:

  1. a test/predicate
    that is applied to a target

  2. pattern variables
    that are extracted from the target
    if the test passes

//        |--------- pattern --------|
// target |----- test ------| variable
    staff instanceof Employee employee

We will see more patterns in the future.

Pattern Variable Scope

Pattern variable is in scope
where compiler can prove pattern is true:

public void inverted(Object object) {
	if (!(object instanceof String string))
		throw new IllegalArgumentException();
	// after inverted test
	System.out.println(string.length());
}

Pattern Variable Scope

public void scoped(Object object) {
	// later in same expression
	if (object instanceof String string
			&& string.length() > 50)
		System.out.println("Long string");

	if (object instanceof String string
			// compiler error because || means
			// it's not necessarily a string
			|| string.length() > 50)
		System.out.println("Maybe string");
}

Null-Check Included

Just like instanceof,
type patterns reject null:

public void nullChecked(Object object) {
	if (object instanceof String string)
		// never NPEs because `string` is not null
		System.out.println(string.length());
}

No Upcasting Allowed

Upcasting makes little sense,
so it’s considered an implementation error:

public void upcast(String string) {
	// compile error
	if (string instanceof CharSequence sequence)
		System.out.println("Duh");
}

What Are The Use Cases?

General recommendation:

Consider classic OOP design
before type patterns.

public void pay(Staff staff) {
	if (staff instanceof Employee employee)
		employee.paySalary();
	else if (staff instanceof Freelancer freelancer)
		freelancer.payBill();
}

public void pay(Staff staff) {
	// method on interface `Staff`
	staff.pay();
}

What Are The Use Cases?

But that doesn’t always work best:

  • handling primitives

  • no control over types

  • OOP solutions can be cumbersome
    (visitor pattern)

What Are The Use Cases?

Another really neat application:

@Override
public final boolean equals(Object o) {
	return o instanceof Type other
		&& someField.equals(other.someField)
		&& anotherField.equals(other.anotherField);
}

More use cases in later versions.

Summary

  • $TARGET instanceof $TYPE $VAR:

    1. checks whether $TARGET is of type $TYPE

    2. creates variable $TYPE $VAR = $TARGET

    3. in scope wherever instanceof $TYPE is true

  • first of many patterns

  • don’t overuse it - polymorphism still exists

  • one pillar of full pattern matching support

New Language Features

Pattern Matching in Switch
Sealed Classes ⑰
Records ⑯
Type Pattern Matching ⑯
Text Blocks ⑮
Switch Expressions ⑭
Local-Variable Type Inference ⑩
All The Small Things... ⑨⑯

Multiline strings. Finally.

Multiline Strings

Text blocks are straightforward:

String haikuBlock = """
	worker bees can leave
	 even drones can fly away
	  the queen is their slave""";
System.out.println(haiku);
// > worker bees can leave
// >  even drones can fly away
// >   the queen is their slave
  • line breaks are normalized to \n

  • intentional indentation remains

  • accidental indentation is removed

Syntax

  • can be used in same place
    as "string literals"

  • start with """ and new line

  • end with """

    • on the last line of content

    • on its own line

Position of closing """ decides
whether string ends with "\n".

Vs String Literals

Compare to:

String haikuLiteral = ""
	+ "worker bees can leave\n"
	+ " even drones can fly away\n"
	+ "  the queen is their slave";
  • haikuBlock.equals(haikuLiteral)

  • thanks to string interning even
    haikuBlock == haikuLiteral

⇝ No way to discern source at run time!

Line Endings

Line ending depends on configuration.
Source file properties influence semantics?

Text block lines always end with \n!

Escape sequences are translated afterwards:

String windows = """
    Windows\r
    line\r
    endings\r
    """

Indentation

Compiler discerns:

  • accidental indentation
    (from code style; gets removed)

  • essential indentation
    (within the string; remains)

How?

Accidental Indentation

  • closing """ are on their own line
    ⇝ their indentation is accidental

  • otherwise, line with smallest indentation
    ⇝ its indentation is accidental

Indentation

Accidental vs intentional indentation
(separated with |):

String haikuBlock = """
		|worker bees can leave
		| even drones can fly away
		|  the queen is their slave""";
String haikuBlock = """
	|	worker bees can leave
	|	 even drones can fly away
	|	  the queen is their slave
	""";

Manual Indentation

To manually manage indentation:

  • String::stripIndent

  • String::indent

Escape Sequences

Text blocks are not raw:

  • escape sequences work (e.g. \r)

  • escape sequences are necessary

But: " is not special!

String phrase = """
    {
        greeting: "hello",
        audience: "text blocks",
    }
    """;

⇝ Way fewer escapes in HTML/JSON/SQL/etc.

More on Text Blocks

New Language Features

Pattern Matching in Switch
Sealed Classes ⑰
Records ⑯
Type Pattern Matching ⑯
Text Blocks ⑮
Switch Expressions ⑭
Local-Variable Type Inference ⑩
All The Small Things... ⑨⑯

More powerful switch.

Switching

Say you’re facing the dreaded ternary Boolean …​

public enum TernaryBoolean {
	TRUE,
	FALSE,
	FILE_NOT_FOUND
}

... and want to convert it to a regular Boolean.

Switch Statement

Before Java 14, you might have done this:

boolean result;
switch (ternaryBool) {
	case TRUE: result = true; break;
	case FALSE: result = false; break;
	case FILE_NOT_FOUND:
		var ex = new UncheckedIOException(
			"This is ridiculous!",
			new FileNotFoundException());
		throw ex;
	default:
		var ex2 = new IllegalArgumentException(
			"Seriously?! 😠");
		throw ex2;
}

Switch Statement

Lots of room for improvements:

  • default fall-through is annoying

  • result handling is roundabout

  • lacking compiler support is error-prone

Switch Statement

This is better:

public boolean convert(TernaryBoolean ternaryBool) {
	switch (ternaryBool) {
		case TRUE: return true;
		case FALSE: return false;
		case FILE_NOT_FOUND:
			throw new UncheckedIOException(
				"This is ridiculous!",
				new FileNotFoundException());
		default:
			throw new IllegalArgumentException(
				"Seriously?! 😠");
	}
}

Switch Statement

Better:

  • return prevents fall-through

  • results are created on the spot

But:

  • default is not really necessary…​

  • …​but prevents compile error
    on missing branches

  • creating a method is not always
    possible or convenient

Switch Expression

Enter switch expressions:

boolean result = switch(ternaryBool) {
    case TRUE -> true;
    case FALSE -> false;
    case FILE_NOT_FOUND ->
		throw new UncheckedIOException(
			"This is ridiculous!",
			new FileNotFoundException());
};

Two things to note:

  • switch "has a result"
    ⇝ it’s an expression, not a statement

  • lambda-style arrow syntax

Expression vs Statement

Statement:

if (condition)
    result = doThis();
else
    result = doThat();

Expression:

result = condition
	? doThis()
	: doThat();

Expression vs Statement

Statement:

  • imperative construct

  • guides computation, but has no result

Expression:

  • is computed to a result

Expression vs Statement

For switch:

  • if used with an assignment,
    switch becomes an expression

  • if used "stand-alone", it’s
    treated as a statement

This results in different behavior
(more on that later).

Arrow vs Colon

You can use : and -> with
expressions and statements, e.g.:

boolean result = switch(ternaryBool) {
    case TRUE: yield true;
    case FALSE: yield false;
    case FILE_NOT_FOUND:
		throw new UncheckedIOException(
			"This is ridiculous!",
			new FileNotFoundException());
};
  • switch is used as an expression

  • yield result returns result

Arrow vs Colon

Whether you use arrow or colon
results in different behavior
(more on that later).

Switch Evolution

  • general improvements

    • multiple case labels

  • specifics of arrow form

    • no fall-through

    • statement blocks

  • specifics of expressions

    • poly expression

    • returning early

    • completeness

Multiple Case Labels

Statements and expressions,
in colon and arrow form
can use multiple case labels:

String result = switch (ternaryBool) {
	case TRUE, FALSE -> "sane";
	// `default, case FILE_NOT_FOUND -> ...`
	// does not work (neither does other way
	// around), but that makes sense because
	// using only `default` suffices
	default -> "insane";
};

No Fall-Through

Whether used as statement or expression,
the arrow form has no fall-through:

switch (ternaryBool) {
	case TRUE, FALSE ->
		System.out.println("Bool was sane");
	// in colon-form, if `ternaryBool` is `TRUE`
	// or `FALSE`, we would see both messages;
	// in arrow-form, only one branch is executed
	default ->
		System.out.println("Bool was insane");
}

Statement Blocks

Whether used as statement or expression,
the arrow form can use statement blocks:

boolean result = switch (ternaryBoolean) {
    case TRUE -> {
        System.out.println("Bool true");
        yield true;
    }
    case FALSE -> {
        System.out.println("Bool false");
        yield false;
    }
	// cases `FILE_NOT_FOUND` and `default`
};

Statement Blocks

Natural way to create scope:

boolean result = switch (ternaryBoolean) {
	// cases `TRUE` and `FALSE`
    case FILE_NOT_FOUND -> {
        var ex = new UncheckedIOException(
            "This is ridiculous!",
            new FileNotFoundException());
        throw ex;
    }
    default -> {
        var ex = new IllegalArgumentException(
            "Seriously?! 🤬");
        throw ex;
    }
};

Poly Expression

A poly expression

  • has no definitive type

  • can be one of several types

Lambdas are poly expressions:

Function<String, String> fun = s -> s + " ";
UnaryOperator<String> op = s -> s + " ";

Poly Expression

Whether in colon or arrow form,
a switch expression is a poly expression.

How it’s type is determined,
depends on the target type:

// target type known: String
String result = switch (ternaryBool) { ... }
// target type unknown
var result = switch (ternaryBool) { ... }

Poly Expression

If target type is known, all branches must conform to it:

String result = switch (ternaryBool) {
    case TRUE, FALSE -> "sane";
    default -> "insane";
};

If target type is unknown, the compiler infers a type:

// compiler infers super type of `String` and
// `IllegalArgumentException` ~> `Serializable`
var serializableMessage = switch (bool) {
    case TRUE, FALSE -> "sane";
    default -> new IllegalArgumentException("insane");
};

Returning Early

Whether in colon or arrow form,
you can’t return early from a switch expression:

public String sanity(Bool ternaryBool) {
    String result = switch (ternaryBool) {
        // compile error:
		//     "return outside
		//      of enclosing switch expression"
        case TRUE, FALSE -> { return "sane"; }
        default -> { return "This is ridiculous!"; }
    };
}

Completeness

Whether in colon or arrow form,
a switch expression checks completeness:

// compile error:
//     "the switch expression does not cover
//      all possible input values"
boolean result = switch (ternaryBool) {
    case TRUE -> true;
    // no case for `FALSE`
    case FILE_NOT_FOUND ->
		throw new UncheckedIOException(
			"This is ridiculous!",
			new FileNotFoundException());
};

Completeness

No compile error for missing default:

// compiles without `default` branch because
// all cases for `ternaryBool` are covered
boolean result = switch (ternaryBool) {
    case TRUE -> true;
    case FALSE -> false;
    case FILE_NOT_FOUND ->
		throw new UncheckedIOException(
			"This is ridiculous!",
			new FileNotFoundException());
};

Compiler adds in default branch.

More on switch

Definitive Guide To Switch Expressions

More powerful and usable switch is
one pillar of pattern matching.

New Language Features

Pattern Matching in Switch
Sealed Classes ⑰
Records ⑯
Type Pattern Matching ⑯
Text Blocks ⑮
Switch Expressions ⑭
Local-Variable Type Inference ⑩
All The Small Things... ⑨⑯

Type inference with var.
Less typing, but still strongly typed.

Type Duplication

We’re used to duplicating
type information:

URL nipafx = new URL("https://nipafx.dev");
URLConnection connection = nipafx.openConnection();
Reader reader = new BufferedReader(
	new InputStreamReader(
		connection.getInputStream()));

Not so bad?

Type Duplication

What about this?

No no = new No();
AmountIncrease<BigDecimal> more =
	new BigDecimalAmountIncrease();
HorizontalConnection<LinePosition, LinePosition>
	jumping =
		new HorizontalLinePositionConnection();
Variable variable = new Constant(5);
List<String> names = List.of("Max", "Maria");

Type Deduplication

Can’t somebody else do that?
Compiler knows the types!

Enter var:

var nipafx = new URL("https://nipafx.dev");
var connection = nipafx.openConnection();
var reader = new BufferedReader(
	new InputStreamReader(
		connection.getInputStream()));

Locality

How much information is used for inference?

  • type inference can be
    arbitrarily complex/powerful

  • critical resource is not
    compiler but developer

  • code should be readable
    (without compiler/IDE)

⇝ Better to keep it simple!

"Action at a distance"

// inferred as `int`
var id = 123;
if (id < 100) {
	// very long branch
} else {
	// oh boy, much more code...
}

// now we add this line:
id = "124";

What type should id be?

Where does the error show up?

Rules of var

Hence, var only works in limited scopes:

  • compiler infers type from right-hand side
    ⇝ rhs has to exist and define a type

  • only works for local variables, for, try
    ⇝ no var on fields or in method signatures

  • also on lambda parameters ⑪
    ⇝ annotate inferred type on lambda parameters

Rules of var

Two more:

  • not a keyword, but a reserved type name
    ⇝ variables/fields can be named var

  • compiler writes type into bytecode
    ⇝ no run-time component

What About Readability?

This is about readability!

  • less redundancy

  • more intermediate variables

  • more focus on variable names

  • aligned variable names

Aligned Variable Names

var no = new No();
var more = new BigDecimalAmountIncrease();
var jumping = new HorizontalLinePositionConnection();
var variable = new Constant(5);
var names = List.of("Max", "Maria");

What About Readability?

Still think omitting types is always bad?

Ever wrote a lambda without declaring types?

rhetoricalQuestion.answer(yes -> "see my point?");

Style Guidelines

Principles from the official style guidelines:

  1. Reading code is more important than writing it.

  2. Code should be clear from local reasoning.

  3. Code readability shouldn’t depend on IDEs.

  4. Explicit types are a tradeoff.

Style Guidelines

Guidelines:

  1. Choose variable names that provide useful info.

  2. Minimize the scope of local variables.

  3. Consider var when the initializer provides sufficient information to the reader.

  4. Use var to break up chained or nested expressions.

  5. Don’t worry too much about "programming to the interface".

  6. Take care when using var with diamonds or generics.

  7. Take care when using var with literals.

Style Guidelines

  1. Choose variable names that provide useful info.

/* ✘ */ var u = UserRepository.findUser(id);
/* ✔ */ var user = UserRepository.findUser(id);
/* 👍*/ var userToLogIn = UserRepository.findUser(id);

Style Guidelines

  1. Minimize the scope of local variables.

// ✘
var id = 123;
if (id < 100) {
	// very long branch
} else {
	// oh boy, much more code...
}
LOGGER.info("... " + id);

// ✔ replace branches with method calls

Style Guidelines

  1. Consider var when the initializer provides
    sufficient information to the reader.

/* ✘ */ var user = Repository.find(id);
/* ✔ */ var user = UserRepository.findUser(id);
/* 👍*/ var user = new User(id);

Style Guidelines

  1. Use var to break up chained or nested expressions.

// ✘
return Canvas
	.activeCanvas()
	.drawings()
	.filter(Drawing::isLine)
	.map(drawing -> (HorizontalConnection) drawing)
		// now we have lines
	.filter(line -> length(line) == 7)
	.map(this::generateSquare)
		// now we have squares
	.map(this::createRandomColoredSquare)
	.map(this::createRandomBorderedSquare)
	.collect(toList());

Style Guidelines

  1. Use var to break up chained or nested expressions.

// ✔
var lines = Canvas
	.activeCanvas()
	.drawings()
	.filter(Drawing::isLine)
	.map(drawing -> (HorizontalConnection) drawing)
var squares = lines
	.filter(line -> length(line) == 7)
	.map(this::generateSquare);
return squares
	.map(this::createRandomColoredSquare)
	.map(this::createRandomBorderedSquare)
	.collect(toList());

Style Guidelines

  1. Don’t worry too much about
    "programming to the interface".

// inferred as `ArrayList` (not `List`),
// but that's ok
var users = new ArrayList<User>();

Careful when refactoring:

  • extracting methods that use var-ed variables
    puts concrete types into method signatures

  • look out and replace with most general type

Style Guidelines

  1. Take care when using var with diamonds or generics.

// ✘ infers `ArrayList<Object>`
var users = new ArrayList<>();

// ✔ infers `ArrayList<User>`
var users = new ArrayList<User>();

Style Guidelines

  1. Take care when using var with literals.

// ✘ when used with `var`, these
//   variables become `int`
byte b = 42;
short s = 42;
long l = 42;

More on var

New Language Features

All The Cool Things... ⑩⑭⑮⑯
Private Interface Methods ⑨
Try-With-Resources ⑨
Diamond Operator ⑨
SafeVarargs ⑨
Deprecation Warnings ⑨⑯

Enabling reuse between default methods.

No Reuse

public interface InJava8 {

	default boolean evenSum(int... numbers) {
		return sum(numbers) % 2 == 0;
	}

	default boolean oddSum(int... numbers) {
		return sum(numbers) % 2 == 1;
	}

	default int sum(int[] numbers) {
		return IntStream.of(numbers).sum();
	}

}

Private Methods

public interface InJava9 {

	private int sum(int[] numbers) {
		return IntStream.of(numbers).sum();
	}

}

Just like private methods in abstract classes:

  • must be implemented

  • can not be overriden

  • can only be called in same source file

New Language Features

All The Cool Things... ⑩⑭⑮⑯
Private Interface Methods ⑨
Try-With-Resources ⑨
Diamond Operator ⑨
SafeVarargs ⑨
Deprecation Warnings ⑨⑯

Making try-with-resources blocks cleaner.

Useless Variable

void doSomethingWith(Connection connection)
		throws Exception {
	try(Connection c = connection) {
		c.doSomething();
	}
}

Why is c necessary?

Why is c necessary?

  • target of close() must be obvious
    ⇝ resource should not be reassigned

  • easiest if resource is final

  • easiest if resource must be assigned
    and can be made implicitly final

try(Connection c = connection)

Effectively Final Resource

But since Java 8 we have effectively final!

This works in Java 9:

void doSomethingWith(Connection connection)
		throws Exception {
	try(connection) {
		connection.doSomething();
	}
}
  • compiler knows that connection is not reassigned

  • developers know what effectively final means

New Language Features

All The Cool Things... ⑩⑭⑮⑯
Private Interface Methods ⑨
Try-With-Resources ⑨
Diamond Operator ⑨
SafeVarargs ⑨
Deprecation Warnings ⑨⑯

A little more type inference.

Diamond Operator

Maybe the best example:

List<String> strings = new ArrayList<>();
  • used at a constructor call

  • tells Java to infer the parametric type

Anonymous Classes

Diamond did not work with anonymous classes:

<T> Box<T> createBox(T content) {
	// we have to put the `T` here :(
	return new Box<T>(content) { };
}

Reason are non-denotable types:

  • might be inferred by compiler
    for anonymous classes

  • can not be expressed by JVM

Infer Denotable Types

Java 9 infers denotable types:

<T> Box<T> createBox(T content) {
	return new Box<>(content) { };
}

Gives compile error if type is non-denotable:

Box<?> createCrazyBox(Object content) {
	List<?> innerList = Arrays.asList(content);
	// compile error
	return new Box<>(innerList) { };
}

New Language Features

All The Cool Things... ⑩⑭⑮⑯
Private Interface Methods ⑨
Try-With-Resources ⑨
Diamond Operator ⑨
SafeVarargs ⑨
Deprecation Warnings ⑨⑯

One less warning you couldn’t do anything about.

Heap Pollution

Innocent looking code…​

private <T> Optional<T> firstNonNull(T... args) {
	return stream(args)
			.filter(Objects::nonNull)
			.findFirst();
}

Compiler warns (on call site, too):

Possible heap pollution from
parameterized vararg type

Heap Pollution?

For generic varargs argument T…​ args,
you must not depend on it being a T[]!

private <T> T[] replaceTwoNulls(
		T value, T first, T second) {
	return replaceAllNulls(value, first, second);
}

private <T> T[] replaceAllNulls(T value, T... args) {
	// loop over `args`, replacing `null` with `value`
	return args;
}

Compiler Warning

Compiler is aware of the problem and warns you.

If you think, everything’s under control:

@SafeVarargs
private <T> Optional<T> firstNonNull(T... args) {
	return // [...]
}

Or not…​ In Java 8 this is a compile error!

Invalid SafeVarargs annotation. Instance
method <T>firstNonNull(T...) is not final.

But Why?

The @SafeVarargs annotation:

  • tells the caller that all is fine

  • only makes sense on methods
    that can not be overriden

Which methods can’t be overriden?
final methods

What about private methods?
⇝ Damn! 😭

@SafeVarargs on Private Methods

Looong story, here’s the point:

In Java 9 @SafeVarargs
can be applied to private methods.

New Language Features

All The Cool Things... ⑩⑭⑮⑯
Private Interface Methods ⑨
Try-With-Resources ⑨
Diamond Operator ⑨
SafeVarargs ⑨
Deprecation Warnings ⑨⑯

Some come, some go.

New Deprecation Warnings

Project Valhalla will bring primitive classes:

  • code like a class, work like an int

  • have no identity

  • allow no identity-based operations

Value-based classes are their precursors.

Identity-based

What is identity-based?

  • constructor calls

  • mutability

  • synchronization

  • serialization

These need to be prevented
for primitive and value-based classes.

Deprecations

Java 16 designates primitive wrapper classes
(Integer, Long, Float, Double, etc)
as value-based classes.

Warning on both lines:

// use Integer::valueOf instead
Integer answer = new Integer(42);
// don't synchronize on values
synchronize(answer) { /*... */ }
  • constructors are deprecated for removal

  • synchronization yields warning

Deprecation Warnings

Should this code emit a warning?

// LineNumberInputStream is deprecated
import java.io.LineNumberInputStream;


public class DeprecatedImports {

    LineNumberInputStream stream;

}
// LineNumberInputStream is deprecated
import java.io.LineNumberInputStream;

@Deprecated
public class DeprecatedImports {

    LineNumberInputStream stream;

}

Not On Imports

Java 9 no longer emits warnings
for importing deprecated members.

Warning free:

import java.io.LineNumberInputStream;

@Deprecated
public class DeprecatedImports {

	LineNumberInputStream stream;

}

Updated APIs

Random Numbers ⑰
Socket Channels ⑯
String ⑪⑫
Stream ⑨⑩⑫⑯
Optional ⑨⑩⑪
OS Processes ⑨
Completable Future ⑫

Existing APIs are continuously improved.

Updated APIs

Random Numbers ⑰
Socket Channels ⑯
String ⑪⑫
Stream ⑨⑩⑫⑯
Optional ⑨⑩⑪
OS Processes ⑨
Completable Future ⑫

Better maintainability and future-proof algorithm selection.

Muddied API

The API before Java 17 was muddied:

  • limited algorithms

  • uneven, e.g. nextLong​(long bound)

  • weird inheritance hierarchy

(The implementations weren’t great, either.)

Weird Inheritance

random types before

RandomGenerator

New central abstraction:
RandomGenerator.

  • API is basically Random
    plus missing methods

  • no requirements for thread-safety
    or cryptographic security

Detailed Subtypes

Subtypes specify how one generator
can create another that is:

  • statistically independent

  • individually uniform

(Or some approximation thereof.)

Updates

Existing classes were updated:

  • implement RandomGenerator or suitable subtype

  • better implementation with less repetition

Algorithms

Current list of pseudorandom number generators (PNRGs):

random algorithms

Algorithm Selection by Name

All interfaces have static factory method of:

var name = "...";

var rg = RandomGenerator.of(name);
var jg = JumpableGenerator.of(name);
var lg = LeapableGenerator.of(name);
// ...

If algorithm of that name and type…​

  • exists ~> it is returned

  • doesn’t exist ~> IllegalArgumentException

Algorithm Evolution

Advances in PRNG development and analysis require flexibility:

  • JDKs can contain more algorithms than listed above

  • new algorithms can be added over time

  • algorithms can be deprecated at any time

  • algorithms can be removed in major versions

⇝ Selection by name is not robust.

Algorithm Selection by Properties

If you have no specific requirements:

var generator = RandomGenerator.getDefault();

With requirements, use RandomGeneratorFactory.

RandomGeneratorFactory

Steps to select an algorithm by properties:

  1. call static method all() to get
    Stream<RandomGeneratorFactory>

  2. use methods to filter by properties, e.g.:

    • isStreamable(), isSplittable(), etc

    • isStatistical(), isStochastic()

    • isHardware()

    • period(), stateBits()

  3. call create() to create the generator

Algorithm Selection by Properties

var generator = RandomGeneratorFactory.all()
	.filter(RandomGeneratorFactory::isJumpable)
	.filter(factory -> factory.stateBits() > 128)
	.findAny()
	.map(RandomGeneratorFactory::create)
//  if you need a `JumpableGenerator`:
//  .map(JumpableGenerator.class::cast)
	.orElseThrow();

New Random Generator API

Summary:

  • interface RandomGenerator as central abstraction

  • more detailed interfaces specify how to
    create one generator from another

  • existing classes implement suitable interfaces

  • list of algorithms is extended and more flexible

  • use RandomGeneratorFactory to select
    algorithms based on properties

Updated APIs

Random Numbers ⑰
Socket Channels ⑯
String ⑪⑫
Stream ⑨⑩⑫⑯
Optional ⑨⑩⑪
OS Processes ⑨
Completable Future ⑫

Now with Unix domain sockets!

Socket Channels

NIO API around SocketChannel/ServerSocketChannel:

  • communication via network sockets

  • can be non-blocking

  • allows multiplexing via selector

New in Java 16: Unix domain sockets.

Unix Domain Sockets

Unix domain sockets:

  • work with filesystem paths

  • only for connections on same host

  • no TCP/IP stack

Server and Client

Server connecting to Unix domain socket:

Path socketFile = Path
	.of(System.getProperty("user.home"))
	.resolve("server.socket");
UnixDomainSocketAddress address =
	UnixDomainSocketAddress.of(socketFile);

ServerSocketChannel serverChannel = ServerSocketChannel
	.open(StandardProtocolFamily.UNIX);
serverChannel.bind(address);

// start sending/receiving messages

Server and Client

Client connecting to Unix domain socket:

Path file = Path
	.of(System.getProperty("user.home"))
	.resolve("server.socket");
UnixDomainSocketAddress address =
	UnixDomainSocketAddress.of(file);

SocketChannel channel = SocketChannel
	.open(StandardProtocolFamily.INET6);
channel.connect(address);

// start sending/receiving messages

Unix Domain Sockets Vs TCP/IP

Unix domain socket are safer and faster:

  • same host ⇝ no inbound connections

  • filesystem paths
    ⇝ detailed, well-understood, OS-enforced permissions

  • no TCP/IP stack ⇝ faster setup, higher throughput

Updated APIs

Random Numbers ⑰
Socket Channels ⑯
String ⑪⑫
Stream ⑨⑩⑫⑯
Optional ⑨⑩⑪
OS Processes ⑨
Completable Future ⑫

Small improvements to String.

Strip White Space ⑪

Getting rid of white space:

String strip();
String stripLeading();
String stripTrailing();

Only at beginning and end of string:

" foo bar ".strip().equals("foo bar");

What About Trim?

Wait, what about trim()?

  • trim() defines white space as:

    any character whose codepoint
    is less than or equal to 'U+0020'
    (the space character)

  • strip() relies on Character::isWhitespace,
    which covers many more cases

Is Blank? ⑪

Is a string only white space?

boolean isBlank();

Functionally equivalent to:

string.isBlank() == string.strip().isEmpty();

Life Hack

As soon as Java APIs get new method,
scour StackOverflow for easy karma!

so repeat string q

Life Hack

Formerly accepted answer:

so repeat string a old

😍

Life Hack ⑪

Ta-da!

so repeat string a

Streaming Lines ⑪

Processing a string’s lines:

Stream<String> lines();
  • splits a string on "\n", "\r", "\r\n"

  • lines do not include terminator

  • more performant than split("\R")

  • lazy!

Changing Indentation ⑫

Use String::indent to add or remove
leading white space:

String oneTwo = " one\n  two\n";
oneTwo.indent(0).equals(" one\n  two\n");
oneTwo.indent(1).equals("  one\n   two\n");
oneTwo.indent(-1).equals("one\n two\n");
oneTwo.indent(-2).equals("one\ntwo\n");

Would have been nice to pass resulting indentation,
not change in indentation.

Changing Indentation ⑫

String::indent normalizes line endings
so each line ends in \n:

"1\n2".indent(0).equals("1\n2\n");
"1\r\n2".indent(0).equals("1\n2\n");
"1\r2\n".indent(0).equals("1\n2\n");
"1\n2\n".indent(0).equals("1\n2\n");

Transforming Strings ⑫

New method on String:

public <R> R transform(Function<String, R> f) {
	return f.apply(this);
}

Use to chain calls instead of nesting them:

User newUser = parse(clean(input));
User newUser = input
	.transform(this::clean)
	.transform(this::parse);

Makes more sense at end of long call chain
(stream pipeline?) to chain more calls.

Transforming things

Maybe other classes get transform, too!
Great for "chain-friendly" APIs like Stream, Optional:

// in a museum...
tourists.stream()
	.map(this::letEnter)
	.transform(this::groupsOfFive)
	.forEach(this::giveTour)

Stream<TouristGroup> groupsOfFive(
	Stream<Tourist> tourists) {
	// this is not trivial,
	// but at least possible
}

⇝ Practice with String::transform!

Updated APIs

Random Numbers ⑰
Socket Channels ⑯
String ⑪⑫
Stream ⑨⑩⑫⑯
Optional ⑨⑩⑪
OS Processes ⑨
Completable Future ⑫

Small improvements to Java 8 streams.

Of Nullable ⑨

Create a stream of zero or one elements:

long zero = Stream.ofNullable(null).count();
long one = Stream.ofNullable("42").count();

Iterate ⑨

To use for even less…​

iterate(
	T seed,
	Predicate<T> hasNext,
	UnaryOperator<T> next);

Example:

Stream
	.iterate(1, i -> i<=10, i -> 2*i)
	.forEach(System.out::println);
// output: 1 2 4 8

Iterate ⑨

Counter Example:

Enumeration<Integer> en = // ...
Stream.iterate(
		en.nextElement(),
		el -> en.hasMoreElements(),
		el -> en.nextElement())
	.forEach(System.out::println);
  • first nextElement()

  • then hasMoreElements()

  • ⇝ fail

Take While ⑨

Stream as long as a condition is true:

Stream<T> takeWhile(Predicate<T> predicate);

Example:

Stream.of("a-", "b-", "c-", "", "e-")
	.takeWhile(s -> !s.isEmpty())
	.forEach(System.out::print);

// output: a-b-c-

Drop While ⑨

Ignore as long as a condition is true:

Stream<T> dropWhile(Predicate<T> predicate);

Example:

Stream.of("a-", "b-", "c-", "de-", "f-")
	.dropWhile(s -> s.length() <= 2)
	.forEach(System.out::print);

// output: de-f-

Collect Unmodifiable ⑩

Create unmodifiable collections
(in the sense of List::of et al)
with Collectors:

Collector<T, ?, List<T>> toUnmodifiableList();

Collector<T, ?, Set<T>> toUnmodifiableSet();

Collector<T, ?, Map<K,U>> toUnmodifiableMap(
	Function<T, K> keyMapper,
	Function<T, U> valueMapper);
// plus overload with merge function

Teeing Collector ⑫

Collect stream elements in two collectors
and combine their results:

// on Collectors
Collector<T, ?, R> teeing(
	Collector<T, ?, R1> downstream1,
	Collector<T, ?, R2> downstream2,
	BiFunction<R1, R2, R> merger);

Teeing Collector ⑫

Example:

Statistics stats = Stream
	.of(1, 2, 4, 5)
	.collect(teeing(
		// Collector<Integer, ?, Integer>
		summingInt(i -> i),
		// Collector<Integer, ?, Double>
		averagingInt(i -> i),
		// BiFunction<Integer, Double, Statistics>
		Statistics::of));
// stats = Statistics {sum=12, average=3.0}

Declarative Flat Map

Stream::flatMap is great, but:

  • sometimes you can’t easily
    map to a Stream

  • creating small/empty streams
    can harm performance

For these niche (!) cases,
there’s Stream::mapMulti.

Imperative Flat Map ⑯

<R> Stream<R> mapMulti​(
	BiConsumer<T, Consumer<R>> mapper)

BiConsumer is called for each element:

  • gets the element T

  • gets a Consumer<R>

  • can pass arbitrarily many R-s
    to the consumer

  • they show up downstream

So like flatMap, but imperative.

Map Multi Examples

Stream.of(1, 2, 3, 4)
	// changes nothing, just passes on elements
	.mapMulti((el, ds) -> ds.accept(el));

Stream
	.of(Optional.of("0"), Optional.empty())
	// unpacks Optionals
	.mapMulti((el, ds) -> el.ifPresent(ds));

Stream
	.of(Optional.of("0"), Optional.empty())
	.mapMulti(Optional::ifPresent);

Type Witness

Unfortunately, mapMulti confuses
parametric type inference:

List<String> strings = Stream
	.of(Optional.of("0"), Optional.empty())
	// without <String>, collect returns List<Object>
	.<String> mapMulti(Optional::ifPresent)
	.collect(toList());

Immediate To List ⑯

How often have you written
.collect(Collectors.toList())?

Answer: too damn often!

But no more:

List<String> strings = Stream
	.of("A", "B", "C")
	// some stream stuff
	.toList()

List Properties

Like collection factories,
the returned lists are:

Unlike them:

  • they can contain null

Updated APIs

Random Numbers ⑰
Socket Channels ⑯
String ⑪⑫
Stream ⑨⑩⑫⑯
Optional ⑨⑩⑪
OS Processes ⑨
Completable Future ⑫

Small improvements to Java 8 Optional.

Or ⑨

Choose a non-empty Optional:

Optional<T> or(Supplier<Optional<T>> supplier);

Find in Many Places

public interface Search {
	Optional<Customer> inMemory(String id);
	Optional<Customer> onDisk(String id);
	Optional<Customer> remotely(String id);

	default Optional<Customer> anywhere(String id) {
		return inMemory(id)
			.or(() -> onDisk(id))
			.or(() -> remotely(id));
	}

}

If Present Or Else ⑨

Like ifPresent but do something if empty:

void ifPresentOrElse(
	Consumer<T> action,
	Runnable emptyAction);

Example:

void logLogin(String id) {
	findCustomer(id)
		.ifPresentOrElse(
			this::logCustomerLogin,
			() -> logUnknownLogin(id));
}

Stream ⑨

Turns an Optional into a Stream
of zero or one elements:

Stream<T> stream();

Filter-Map …​

private Optional<Customer> findCustomer(String id) {
	// ...
}

Stream<Customer> findCustomers(List<String> ids) {
	return ids.stream()
		.map(this::findCustomer)
		// now we have a Stream<Optional<Customer>>
		.filter(Optional::isPresent)
		.map(Optional::get)
}

…​ in one Step

private Optional<Customer> findCustomer(String id) {
	// ...
}

Stream<Customer> findCustomers(List<String> ids) {
	return ids.stream()
		.map(this::findCustomer)
		// now we have a Stream<Optional<Customer>>
		// we can now filter-map in one step
		.flatMap(Optional::stream)
}

From Eager to Lazy

List<Order> getOrders(Customer c) is expensive:

List<Order> findOrdersForCustomer(String id) {
	return findCustomer(id)
		.map(this::getOrders) // eager
		.orElse(new ArrayList<>());
}

Stream<Order> findOrdersForCustomer(String id) {
	return findCustomer(id)
		.stream()
		.map(this::getOrders) // lazy
		.flatMap(List::stream);
}

Or Else Throw ⑩

Optional::get invites misuse
by calling it reflexively.

Maybe get wasn’t the best name?
New:

T orElseThrow()

Works exactly as get,
but more self-documenting.

Aligned Names

Name in line with other accessors:

T orElse(T other)
T orElseGet(Supplier<T> supplier)
T orElseThrow()
	throws NoSuchElementException
T orElseThrow(
	Supplier<EX> exceptionSupplier)
	throws EX

Get Considered Harmful

JDK-8160606 will deprecate
Optional::get.

  • when?

  • for removal?

We’ll see…​

Is Empty ⑪

No more !foo.isPresent():

boolean isEmpty()

Does exactly what
you think it does.

Updated APIs

Random Numbers ⑰
Socket Channels ⑯
String ⑪⑫
Stream ⑨⑩⑫⑯
Optional ⑨⑩⑪
OS Processes ⑨
Completable Future ⑫

Improving interaction with OS processes.

Simple Example

ls /home/nipa/tmp | grep pdf
Path dir = Paths.get("/home/nipa/tmp");
ProcessBuilder ls = new ProcessBuilder()
		.command("ls")
		.directory(dir.toFile());
ProcessBuilder grepPdf = new ProcessBuilder()
		.command("grep", "pdf")
		.redirectOutput(Redirect.INHERIT);
List<Process> lsThenGrep = ProcessBuilder
		.startPipeline(List.of(ls, grepPdf));

Extended Process

Cool new methods on Process:

  • boolean supportsNormalTermination();

  • long pid();

  • CompletableFuture<Process> onExit();

  • Stream<ProcessHandle> children();

  • Stream<ProcessHandle> descendants();

  • ProcessHandle toHandle();

New ProcessHandle

New functionality actually comes from ProcessHandle.

Interesting static methods:

  • Stream<ProcessHandle> allProcesses();

  • Optional<ProcessHandle> of(long pid);

  • ProcessHandle current();

More Information

ProcessHandle can return Info:

  • command, arguments

  • start time

  • CPU time

Updated APIs

Random Numbers ⑰
Socket Channels ⑯
String ⑪⑫
Stream ⑨⑩⑫⑯
Optional ⑨⑩⑪
OS Processes ⑨
Completable Future ⑫

Asynchronous error recovery.

Recap on API Basics

// start an asynchronous computation
public static CompletableFuture<T> supplyAsync(
	Supplier<T>);

// attach further steps
public CompletableFuture<U> thenApply(Function<T, U>);
public CompletableFuture<U> thenCompose(
	Function<T, CompletableFuture<U>);
public CompletableFuture<Void> thenAccept(Consumer<T>);

// wait for the computation to finish and get result
public T join();

Recap on API Basics

Example:

public void loadWebPage() {
	String url = "https://nipafx.dev";
	CompletableFuture<WebPage> future = CompletableFuture
			.supplyAsync(() -> webRequest(url))
			.thenApply(html -> new WebPage(url, html));
	// ... do other stuff
	WebPage page = future.join();
}

private String webRequest(String url) {
	// make request to URL and return HTML
	// (this can take a while)
}

Recap on Completion

A pipeline or stage completes when
the underlying computation terminates.

  • it completes normally if
    the computation yields a result

  • it completes exceptionally if
    the computation results in an exception

Recap on Error Recovery

Two methods to recover errors:

// turn the error into a result
CompletableFuture<T> exceptionally(Function<Throwable, T>);
// turn the result or error into a new result
CompletableFuture<U> handle(BiFunction<T, Throwable, U>);

They turn exceptional completion of the previous stage
into normal completion of the new stage.

Recap on Error Recovery

Example:

loadUser(id)
	.thenCompose(this::loadUserHistory)
	.thenCompose(this::createRecommendations)
	.exceptionally(ex -> {
		log.warn("Recommendation error", ex)
		return createDefaultRecommendations();
	})
	.thenAccept(this::respondWithRecommendations);

Composeable Error Recovery ⑫

Error recovery accepts functions
that produce CompletableFuture:

exceptionallyCompose(
	Function<Throwable, CompletionStage<T>>)

Recap on (A)Synchronicity

Which threads actually compute the stages?

  • supplyAsync(Supplier<T>) is executed
    in the common fork/join pool

  • for other stages it’s undefined:

    • could be the same thread as the previous stage

    • could be another thread in the pool

    • could be the thread calling thenAccept et al.

How to force async computation?

Recap on (A)Synchronicity

All "composing" methods
have an …​Async companion, e.g.:

thenApplyAsync(Function<T, U>);
thenAcceptAsync(Consumer<T>);

They submit each stage as a separate task
to the common fork/join pool.

Async Error Recovery ⑫

Error recovery can be asynchronous:

CompletableFuture<T> exceptionallyAsync(
	Function<Throwable, T>)
CompletableFuture<T> exceptionallyComposeAsync(
	Function<Throwable, CompletableFuture<T>>)

There are overloads that accept Executor.

Even More Updated APIs

Two great sources on
Java API changes
between versions:

Even More Updated APIs

In Java 9:

  • OASIS XML Catalogs 1.1 (JEP 268),
    Xerces 2.11.0 (JEP 255)

  • Unicode support in
    PropertyResourceBundle (JEP 226)

Many lower-level APIs.

Even More New I/O Methods

In Java 9 to 11:

Path.of(String); // ~ Paths.get(String) ⑪

Files.readString(Path); // ⑪
Files.writeString(Path, CharSequence, ...); // ⑪

Reader.transferTo(Writer); // ⑩
InputStream.transferTo(OutputStream); // ⑨

Reader.nullReader(); // ⑪
Writer.nullWriter(); // ⑪
InputStream.nullInputStream(); // ⑪
OutputStream.nullOutputStream(); // ⑪

Even More New I/O Methods

In Java 12 and 13:

Files.mismatch(Path, Path); // ⑫

FileSystems.newFileSystem(Path, ...); // ⑬

ByteBuffer.get(int, ...) // ⑬
ByteBuffer.put(int, ...) // ⑬

Even More New Math Methods

// in Java 14
StrictMath.decrementExact(int);
StrictMath.decrementExact(long);
StrictMath.incrementExact(int);
StrictMath.incrementExact(long);
StrictMath.negateExact(int);
StrictMath.negateExact(long);

// in Java 15
Math.absExact(int)
Math.absExact(long)
StrictMath.absExact(int)
StrictMath.absExact(long)

Even More New Methods

In Java 10:

DateTimeFormatter.localizedBy(Locale);

In Java 11:

Collection.toArray(IntFunction<T[]>);
Predicate.not(Predicate<T>); // static
Pattern.asMatchPredicate(); // ⇝ Predicate<String>

Even More New Methods

In Java 12:

  • NumberFormat::getCompactNumberInstance

In Java 15:

// instance version of String::format
String.formatted(Object... args);

Even More New Methods

In Java 16:

Objects.checkIndex(long, long)
Objects.checkFromToIndex(long, long, long)
Objects.checkFromIndexSize(long, long, long)

New APIs

Vector
Collection Factories ⑨⑩
Reactive Streams ⑨
Reactive HTTP/2 Client ⑪⑯
Stack-Walking ⑨

New APIs are added over time.

New APIs

Vector
Collection Factories ⑨⑩
Reactive Streams ⑨
Reactive HTTP/2 Client ⑪⑯
Stack-Walking ⑨

Make full use of your CPUs.

Vector API

[E]xpress vector computations that reliably compile at runtime to optimal vector hardware instructions on supported CPU architectures and thus achieve superior performance to equivalent scalar computations

— JEP 338

Vector API

I don’t have much on this
but others do:

New APIs

Vector
Collection Factories ⑨⑩
Reactive Streams ⑨
Reactive HTTP/2 Client ⑪⑯
Stack-Walking ⑨

Easy creation of ad-hoc collections.

Hope is Pain

Wouldn’t this be awesome?

List<String> list = [ "a", "b", "c" ];
Map<String, Integer> map = [ "one" = 1, "two" = 2 ];

Not gonna happen!

  • language change is costly

  • binds language to collection framework

  • strongly favors specific collections

Next Best Thing ⑨

List<String> list = List.of("a", "b", "c");
Map<String, Integer> mapImmediate = Map.of(
		"one", 1,
		"two", 2,
		"three", 3);
Map<String, Integer> mapEntries = Map.ofEntries(
		Map.entry("one", 1),
		Map.entry("two", 2),
		Map.entry("three", 3));

Interesting Details

  • collections are immutable
    (no immutability in types, though)

  • collections are value-based

  • null elements/keys/values are forbidden

  • iteration order is random between JVM starts
    (except for lists, of course!)

Immutable Copies ⑩

Creating immutable copies:

/* on List */ List<E> copyOf(Collection<E> coll);
/* on Set */ Set<E> copyOf(Collection<E> coll);
/* on Map */ Map<K, V> copyOf(Map<K,V> map);

Great for defensive copies:

public Customer(List<Order> orders) {
	this.orders = List.copyOf(orders);
}

New APIs

Vector
Collection Factories ⑨⑩
Reactive Streams ⑨
Reactive HTTP/2 Client ⑪⑯
Stack-Walking ⑨

The JDK as common ground
for reactive stream libraries.

Reactive Types

Publisher
  • produces items to consume

  • can be subscribed to

Subscriber
  • subscribes to publisher

  • onNext, onError, onComplete

Subscription
  • connection from subscriber to publisher

  • request, cancel

Reactive Flow

Subscribing

  • create Publisher pub and Subscriber sub

  • call pub.subscribe(sub)

  • pub creates Subscription script
    and calls sub.onSubscription(script)

  • sub can store script

Reactive Flow

Streaming

  • sub calls script.request(10)

  • pub calls sub.onNext(element) (max 10x)

Canceling

  • pub may call sub.OnError(err)
    or sub.onComplete()

  • sub may call script.cancel()

Reactive APIs?

JDK only provides three interfaces
and one simple implementation.

(Also called Flow API.)

So far, only reactive HTTP/2 API ⑪ uses Flow.

New APIs

Vector
Collection Factories ⑨⑩
Reactive Streams ⑨
Reactive HTTP/2 Client ⑪⑯
Stack-Walking ⑨

HTTP/2! And reactive! Woot!

Basic Flow

To send a request and get a response:

  • use builder to create immutable HttpClient

  • use builder to create immutable HttpRequest

  • pass request to client to receive HttpResponse

Building a Client

HttpClient client = HttpClient.newBuilder()
	.version(HTTP_2)
	.connectTimeout(ofSeconds(5))
	.followRedirects(ALWAYS)
	.build();

More options:

  • proxy

  • SSL context/parameters

  • authenticator

  • cookie handler

Building a Request

HttpRequest request = HttpRequest.newBuilder()
	.GET()
	.uri(URI.create("https://nipafx.dev"))
	.setHeader("header-name", "header-value")
	.build();
  • more HTTP methods (duh!)

  • individual timeout

  • individual HTTP version

  • request "100 CONTINUE" before sending body

  • create prefilled builder from existing request ⑯

Receiving a Response

// the generic `String`...
HttpResponse<String> response = client.send(
	request,
	// ... comes from this body handler ...
	BodyHandlers.ofString());
// ... and defines `body()`s return type
String body = response.body();
  • status code, headers, SSL session

  • request

  • intermediate responses
    (redirection, authentication)

Reactive?

Great, but where’s the reactive sauce?

Three places:

  • send request asynchronously

  • provide request body with
    Publisher<ByteBuffer>

  • receive response body with
    Subscriber<String> or
    Subscriber<List<ByteBuffer>>

Asynchronous Request

Submit request to thread pool until completes:

CompletableFuture<String> responseBody = client
	.sendAsync(request, BodyHandlers.ofString())
	.thenApply(this::logHeaders)
	.thenApply(HttpResponse::body);
  • uses "a default executor" to field requests

  • pool can be defined when client is built with
    HttpClient.Builder.executor(Executor)

Reactive Request Body

If a request has a long body,
no need to prepare it in its entirety:

Publisher<ByteBuffer> body = // ...
HttpRequest post = HttpRequest.newBuilder()
	.POST(BodyPublishers.fromPublisher(body))
	.build();
client.send(post, BodyHandlers.ofString())
  • client subscribes to body

  • as body publishes byte buffers,
    client sends them over the wire

Reactive Response Body

If a response has a long body,
no need to wait before processing:

Subscriber<String> body = // ...
HttpResponse<Void> response = client.send(
	request,
	BodyHandlers.fromLineSubscriber(subscriber));
  • client subscribes body to itself

  • as client receives response bytes,
    it parses to lines and passes to body

Reactive Benefits

Benefits of reactive
request/response bodies:

  • receiver applies backpressure:

    • on requests, client

    • on responses, body

  • body controls memory usage

  • early errors lead to partial processing

  • need "reactive tools" to create body
    from higher-level Java objects (e.g. File)

Web Sockets

Short version:

  • there’s a class WebSocket

  • send[Text|Binary|…​] methods
    return CompletableFuture

  • socket calls Listener methods
    on[Text|Binary|…​]

(WebSocket and Listener behave like
Subscription and Subscriber.)

No long version. 😛

New APIs

Vector
Collection Factories ⑨⑩
Reactive Streams ⑨
Reactive HTTP/2 Client ⑪⑯
Stack-Walking ⑨

Examining the stack faster and easier.

StackWalker::forEach

void forEach (Consumer<StackFrame>);
public static void main(String[] args) { one(); }
static void one() { two(); }
static void two() {
	StackWalker.getInstance()
		.forEach(System.out::println);
}

// output
StackWalkingExample.two(StackWalking.java:14)
StackWalkingExample.one(StackWalking.java:11)
StackWalkingExample.main(StackWalking.java:10)

StackWalker::walk

T walk (Function<Stream<StackFrame>, T>);
static void three() {
	String line = StackWalker.getInstance().walk(
		frames -> frames
			.filter(f -> f.getMethodName().contains("one"))
			.findFirst()
			.map(f -> "Line " + f.getLineNumber())
			.orElse("Unknown line");
	);
	System.out.println(line);
}

// output
Line 11

Options

getInstance takes options as arguments:

  • SHOW_REFLECT_FRAMES for reflection frames

  • SHOW_HIDDEN_FRAMES e.g. for lambda frames

  • RETAIN_CLASS_REFERENCE for Class<?>

Frames and Traces

forEach and walk operate on StackFrame:

  • class and method name

  • class as Class<?>

  • bytecode index and isNative

Can upgrade to StackTraceElement (expensive):

  • file name and line number

Performance I

stack walker vs exception

Performance II

stack walker limit with estimated size

Performance III

  • creating StackTraceElement is expensive
    (for file name and line number)

  • lazy evaluation pays off for partial traversal

(Benchmarks performed by Arnaud Roger)

Even More New APIs

In Java 9:

Even More New APIs

In Java 12:

In Java 14:

  • foreign-memory access (JEP 370)

  • non-volatile MappedByteBuffer (JEP 352)

And often lots of low-level APIs.

JVM Features

Packaging Tool ⑯
Strong Encapsulation ⑯
Helpful NPEs ⑭
Launch Source File ⑪
Unified Logging ⑨
Multi-Release JARs ⑨
Redirected Platform Logging ⑨

Continuous improvements to the JVM and its tools.

JVM Features

Packaging Tool ⑯
Strong Encapsulation ⑯
Helpful NPEs ⑭
Launch Source File ⑪
Unified Logging ⑨
Multi-Release JARs ⑨
Redirected Platform Logging ⑨

Creating self-contained, OS-typical Java apps.

Packaging Tool

JVM Features

Packaging Tool ⑯
Strong Encapsulation ⑯
Helpful NPEs ⑭
Launch Source File ⑪
Unified Logging ⑨
Multi-Release JARs ⑨
Redirected Platform Logging ⑨

JDK internals - hidden by default.

Strong Encapsulation

Recap:

  • all JDK code is in modules

  • only public classes/members in
    exported packages can be accessed

  • static or reflective access to others is illegal

But…​

Permitted Illegal Access

But JVM can make an exception for:

  • code on the class path

  • that accesses pre-JDK-9 packages

Can be configured with --illegal-access.

Illegal Access Modes

Four modes:

  • permit: warning on first reflective access to package

  • warn: warning on each reflective access

  • debug: like warn plus stack trace

  • deny: illegal access denied (static + reflective)

First three permit static access.

Configure Illegal Access

Configuration:

  • in Java 9-15, permit is default

  • in Java 16+, deny is default

  • in Java ??, the option will be removed

  • can use --illegal-access to override
    (but don’t; instead fix illegal access!)

$ java --class-path 'mods/*'
    --illegal-access=deny
    advent.Main

JVM Features

Packaging Tool ⑯
Strong Encapsulation ⑯
Helpful NPEs ⑭
Launch Source File ⑪
Unified Logging ⑨
Multi-Release JARs ⑨
Redirected Platform Logging ⑨

Finally can NPEs be helpful!

Typical NPEs

java.lang.NullPointerException
		at dev.nipafx.Regular.doing(Regular.java:28)
		at dev.nipafx.Business.its(Business.java:20)
		at dev.nipafx.Code.thing(Code.java:11)

Ok-ish for coders, but suck for everybody else.

Helpful NPEs

With -XX:+ShowCodeDetailsInExceptionMessages:

java.lang.NullPointerException:
	Cannot invoke "String.length()" because the return
	value of "dev.nipafx.Irregular.doing()"
	is null
		at dev.nipafx.Regular.doing(Regular.java:28)
		at dev.nipafx.Business.its(Business.java:20)
		at dev.nipafx.Code.thing(Code.java:11)

Why the flag?

The command line option
is needed (for now), because:

  • performance

  • security

  • compatibility

But:

It is intended to enable code details
in exception messages by default
in a later release.

JVM Features

Packaging Tool ⑯
Strong Encapsulation ⑯
Helpful NPEs ⑭
Launch Source File ⑪
Unified Logging ⑨
Multi-Release JARs ⑨
Redirected Platform Logging ⑨

Faster feedback with fewer tools.

Launching A Single Source File

Compiling and running
simple Java programs is verbose.

Not any more!

java HelloJava11.java

Background

How it works:

  • compiles source into memory

  • runs from there

Details:

  • requires module jdk.compiler

  • processes options like class/module path et al.

  • interprets @files for easier option management

Use Cases

Mostly similar to jshell:

  • easier demonstrations

  • more portable examples

  • experimentation with new language features
    (combine with --enable-preview)

But also: script files!

Scripts

Steps towards easier scripting:

  • arbitrary file names

  • shebang support

Arbitrary File Names

Use --source if file doesn’t end in .java:

java --source 11 hello-java-11

Shebang Support

To create "proper scripts":

  • include shebang in source:

    #!/opt/jdk-11/bin/java --source 11
  • name script and make it executable

  • execute it as any other script:

    # from current directory:
    ./hello-java-11
    # from PATH:
    hello-java-11

JVM Features

Packaging Tool ⑯
Strong Encapsulation ⑯
Helpful NPEs ⑭
Launch Source File ⑪
Unified Logging ⑨
Multi-Release JARs ⑨
Redirected Platform Logging ⑨

Observing the JVM at work.

Unified Logging

New logging infrastructure for the JVM
(e.g. OS interaction, threading, GC, etc.):

  • JVM log messages pass through new mechanism

  • works similar to known logging frameworks:

    • textual messages

    • log level

    • time stamps

    • meta information (like subsystem)

  • output can be configured with -Xlog

Unified Logging

unified logging

First Try

Plain use of -Xlog:

$ java -Xlog -version

# truncated a few messages
> [0.002s][info][os       ] HotSpot is running ...
# truncated a lot of messages

You can see:

  • JVM uptime (2ms)

  • log level (info)

  • tags (os)

  • message

Configuring -Xlog

This can be configured:

  • which messages to show

  • where messages go

  • what messages should say

How? -Xlog:help lists all options.

Which Messages?

Configure with selectors: $TAG_SET=$LEVEL:

# "exactly gc" on "warning"
-Xlog:gc=warning
# "including gc" on "warning"
-Xlog:gc*=warning
# "exactly gc and os" on "debug"
-Xlog:gc+os=debug
# "gc" on "debug" and "os" on warning
-Xlog:gc=debug,os=warning

Defaults:

-Xlog       # the same as -Xlog:all
-Xlog:$TAG  # same as -Xlog:$TAG=info

Where Do Messages Go?

Three possible locations:

  • stdout (default)

  • stderr

  • file=$FILENAME
    (file rotation is possible)

Example:

# all debug messages into application.log
-Xlog:all=debug:file=application.log

Which Information?

Decorators define what is shown:

  • time: time and date (also in ms and ns)

  • uptime: time since JVM start (also in ms and ns)

  • pid: process identifier

  • tid: thread identifier

  • level: log level

  • tags: tag-set

Example:

# show uptime in ms and level
-Xlog:all:stdout:level,uptimemillis

Put Together

Formal syntax:

-Xlog:$SELECTORS:$OUTPUT:$DECORATORS:$OUTPUT_OPTS
  • $SELECTORS are pairs of tag sets and log levels
    (the docs call this what-expression)

  • $OUTPUT is stdout, stderr, or file=<filename>

  • $DECORATORS define what is shown

  • $OUTPUT_OPTS configure file rotation

Elements have to be defined from left to right.
(But can be empty, e.g. -Xlog::stderr.)

JVM Features

Packaging Tool ⑯
Strong Encapsulation ⑯
Helpful NPEs ⑭
Launch Source File ⑪
Unified Logging ⑨
Multi-Release JARs ⑨
Redirected Platform Logging ⑨

"Do this on Java X, do that on Java Y."

Version Dependence

Main calls Version:

public class Main {

	public static void main(String[] args) {
		System.out.println(new Version().get());
	}

}

Version Dependence

Version exists twice:

public class Version {

	public String get() { return "Java 8"; }

}

public class Version {

	public String get() { return "Java 9+"; }

}

(Btw, IDEs hate this!)

Creating A Multi‑Release JAR

Now, here’s the magic:

  • compile Main and Version[8] to out/java-8

  • compile Version[9] to out/java-9

  • use new jar flag --release:

    jar --create --file out/mr.jar
    	-C out/java-8 .
    	--release 9 -C out/java-9 .

JAR Content

└ dev
    └ nipafx ... (moar folders)
        ├ Main.class
        └ Version.class # 8
└ META-INF
    └ versions
        └ 9
            └ dev
                └ nipafx ... (moar folders)
                    └ Version.class # 9

Run!

With java -cp out/mr.jar …​Main:

  • prints "Java 8" on Java 8

  • prints "Java 9+" on Java 9 and later

Great Success!

JVM Features

Packaging Tool ⑯
Strong Encapsulation ⑯
Helpful NPEs ⑭
Launch Source File ⑪
Unified Logging ⑨
Multi-Release JARs ⑨
Redirected Platform Logging ⑨

Use your logging framework of choice
as backend for JDK logging.

Loggers and Finders

New logging infrastructure for the core libraries
(i.e. this does not apply to JVM log messages!)

  • new interface System.Logger

  • used by JDK classes

  • instances created by System.LoggerFinder

The interesting bit:

LoggerFinder is a service!

Creating a Logger

public class SystemOutLogger implements Logger {

	public String getName() { return "SystemOut"; }

	public boolean isLoggable(Level level) { return true; }

	public void log(
			Level level, ResourceBundle bundle,
			String format, Object... params) {
		System.out.println(/* ...*/);
	}

	// another, similar `log` method

}

Creating a LoggerFinder

public class SystemOutLoggerFinder
		extends LoggerFinder {

	public Logger getLogger(
			String name, Module module) {
		return new SystemOutLogger();
	}

}

Registering the Service

Module descriptor for system.out.logger:

module system.out.logger {
    provides java.lang.System.LoggerFinder
        with system.out.logger.SystemOutLoggerFinder;
}

Module system and JDK take care of the rest!

Even More New JVM Features

In Java 9:

Even More New JVM Features

In Java 10:

  • alternative memory device support (JEP 316)

In Java 11:

Even More New JVM Features

In Java 12:

In Java 15:

Performance

Garbage Collection Improvements ◎
Application Class-Data Sharing ⑩⑫⑬
Compact Strings ⑨
Indified String Concatenation ⑨

Performance

Garbage Collection Improvements ◎
Application Class-Data Sharing ⑩⑫⑬
Compact Strings ⑨
Indified String Concatenation ⑨

Each Java version sees (incremental) GC improvements.

GC Improvements

All following diagrams come
from Stefan Johansson's post
GC progress from JDK 8 to JDK 17.

Performance

Garbage Collection Improvements ◎
Application Class-Data Sharing ⑩⑫⑬
Compact Strings ⑨
Indified String Concatenation ⑨

Improving application launch times.

Class-Data

JVM steps to execute a class’s bytecode:

  • looks up class in JAR

  • loads bytecode

  • verifies bytecode

  • stores class-data in
    internal data structure

This takes quite some time.

If classes don’t change, the resulting
class-data is always the same!

Class-Data Sharing

Idea behind class-data sharing:

  • create class-data once

  • dump it into an archive

  • reuse the archive in future launches
    (file is mapped into memory)

Effects

My experiments with a large desktop app
(focusing on classes required for launch):

  • archive has 250 MB for ~24k classes

  • launch time reduced from 15s to 12s

Bonus: Archive can be shared across JVMs.

Class-Data Sharing

Two variants:

CDS

just for JDK classes

AppCDS

JDK + application classes

CDS - Step #1

Create JDK archive on Java 10/11:

# possibly as root
java -Xshare:dump

Java 12+ downloads include
CDS archive for JDK classes.

CDS - Step #2

Use the archive:

$ java
	-Xshare:on
	# [... class path for app and deps ...]
	org.example.Main

If archive is missing or faulty:

  • -Xshare:on fails fast

  • -Xshare:auto (default) ignores archive

(Slides rely on default, i.e. no -Xshare.)

AppCDS

Create an AppCDS archive:

  • manually on Java 10+

  • dynamically on Java 13+

First manually, then dynamically.

AppCDS - Step #0

To manually create an AppCDS archive,
first create a list of classes

$ java
	-XX:DumpLoadedClassList=classes.lst
	# [... class path for app and deps ...]
	org.example.Main

Then, classes.lst contains
slash-separated names of loaded classes.

AppCDS - Step #1

Use the list to create the archive:

$ java
	-Xshare:dump
	-XX:SharedClassListFile=classes.lst
	-XX:SharedArchiveFile=app-cds.jsa
	# [... class path for app and deps ...]

Creates archive app-cds.jsa.

AppCDS - Step #2

Use the archive:

$ java
	-XX:SharedArchiveFile=app-cds.jsa
	# [... class path for app and deps ...]
	org.example.Main

Dynamic AppCDS

Java 13 can create archive when
program exits (without crash):

  • steps #0 and #1 are replaced by:

    $ java
    	-XX:ArchiveClassesAtExit=dyn-cds.jsa
    	# [... class path for app and deps ...]
    	org.example.Main
  • step #2 as before:

    $ java
    	-XX:SharedArchiveFile=app-cds.jsa
    	# [... class path for app and deps ...]
    	org.example.Main

Dynamic AppCDS

The dynamic archive:

  • builds on the JDK-archive

  • contains all loaded app/lib classes

  • including those loaded by
    user-defined class loaders

Heed The Class Path

What are the two biggest challenges
in software development?

  1. naming

  2. cache invalidation

  3. off-by-one errors

Heed The Class Path

The archive is a cache!

It’s invalid when:

  • a JAR is updated

  • class path is reordered

  • a JAR is added
    (unless when appended)

Heed The Class Path

To invalidate the archive:

  • during creation:

    • Java stores used class path in archive

    • class path may not contain wild cards

    • class path may not contain exploded JARs

  • when used:

    • Java checks whether stored path
      is prefix of current path

Module Path?

Class path, class path…​
what about the module path?

In this release, CDS cannot archive classes from user-defined modules (such as those specified in --module-path). We plan to add that support in a future release.

More On (App)CDS

For more, read this article:
tiny.cc/app-cds

Observe sharing with
-Xlog:class+load
(unified logging)

Performance

Garbage Collection Improvements ◎
Application Class-Data Sharing ⑩⑫⑬
Compact Strings ⑨
Indified String Concatenation ⑨

Going from UTF-16 to ISO-8859-1.

Strings and memory

  • 20% - 30% of heap are char[] for String

  • a char is UTF-16 code unit ⇝ 2 bytes

  • most strings only require ISO-8859-1 ⇝ 1 byte

10% - 15% of memory is wasted!

Compact Strings

For Java 9, String was changed:

  • uses byte[] instead of char[]

  • bytes per character:

    • 1 if all characters are ISO-8859-1

    • 2 otherwise

Only possible because String makes
defensive copies of all arguments.

Performance

Simple benchmark:
(by Aleksey Shipilëv)

String method = generateString(size);

public String work() {
	return "Calling method \"" + method + "\"";
}

Depending on circumstances:

  • throughput 1.4x

  • garbage less 1.85x

More

Background on String
performance improvements:

Performance

Garbage Collection Improvements ◎
Application Class-Data Sharing ⑩⑫⑬
Compact Strings ⑨
Indified String Concatenation ⑨

"Improving" + "String" + "Concatenation"

String Concatenation

What happens when you run:

String s = greeting + ", " + place + "!";
  • bytecode uses StringBuilder

  • JIT may (!) recognize and optimize
    by writing content directly to new byte[]

  • breaks down quickly
    (e.g. with long or double)

Why Not Create Better Bytecode?

  • new optimizations create new bytecode

  • new optimizations require recompile

  • test matrix JVMs vs bytecodes explodes

Why Not Call String::concat?

There is no such method.

  • concat(String…​ args) requires toString

  • concat(Object…​ args) requires boxing

Nothing fancy can be done
because compiler must use public API.

Invokedynamic

Invokedynamic came in Java 7:

  • compiler creates a recipe

  • runtime has to process it

  • defers decisions from compiler to runtime

(Used for lambda expressions and in Nashorn.)

Indy To The Rescue

With Indy compiler can express
"concat these things"
(without boxing!)

JVM executes by writing content
directly to new byte[].

Performance

Depending on circumstances:

  • throughput 2.6x

  • garbage less 3.4x

(Benchmarks by Aleksey Shipilëv)

Performance Of Indified Compact String Concat

Depending on circumstances:

  • throughput 2.9x

  • garbage less 6.4x

(Benchmarks by Aleksey Shipilëv)

More

Background on String
performance improvements:

Even More Performance

In Java 9:

Even More Performance

In Java 10:

In Java 11:

  • low-overhead heap profiling (JEP 331)

  • open-source Flight Recorder (JEP 328)

In Java 14:

Garbage Collectors

G1, ZGC and Shenandoah are actively developed
and become faster in each release.

Even More Performance

And many, many smaller changes.

  • new releases are generally faster

  • in the cloud:
    less CPU/memory ⇝ lower costs

⇝ Updating saves money!

(And you’re doing it anyway sooner or later.)

So long…​

37% off with
code fccparlog

bit.ly/the-jms

More

Slides at slides.nipafx.dev
⇜ Get my book!

Follow Nicolai

nipafx.dev
/nipafx

Follow Java

inside.java
/java    //    /openjdk

Image Credits