Eleven Hidden Gems In Java 11

Think Java 11 is just a maintenance release? Think again! Here are eleven small but shiny additions to classes like String, Path, Files, Collection, Optional, and others that make coding a little more elegant.

Java 11 introduced no ground-breaking features, but contains a number of gems that you may not have heard about yet. Sure, you likely know about the reactive HTTP/2 API and executing source files without compiling them, but did you try out the additions to String, Optional, Collection, and other workhorses? If not, you've come to the right place: Here are eleven hidden gems in Java 11!

Eleven Gems

Type Inference For Lambda Parameters

When writing a lambda expression, you can choose between specifying the types or omitting them:

Function<String, String> append = string -> string + " ";
Function<String, String> append = (String s) -> s + " ";

Java 10 introduced var, but you couldn't use it in lambdas:

// compile error in Java 10
Function<String, String> append = (var string) -> string + " ";

In Java 11 you can. Why, though? It's not like var adds anything over just omitting the type. While that is the case, allowing var has two minor advantages:

  • makes the mental model for var more uniform by removing a special case
  • allows type annotations on lambda parameters without having to resort to a full type name

Here's an example for the second point:

List<EnterpriseGradeType<With, Generics>> types = /*...*/;
types.stream()
	// this is fine, but we need @Nonnull on the type
	.filter(type -> check(type))
	// in Java 10, we need to do this ~> ugh!
	.filter((@Nonnull EnterpriseGradeType<With, Generics> type) -> check(type))
	// in Java 11, we can do this ~> better
	.filter((@Nonnull var type) -> check(type))

While mixing implicit types, explicit types, and var in lambdas like (var type, String option, index) -> ... could be supported, it would (apparently) make the implementation more complicated. You hence have to choose one of the three approaches and stick with it for all parameters. Having to add var to all parameters just to apply an annotation to one of them, may be mildly annoying, but I think it's bearable.

Streaming Lines With String::lines

Got a multiline string? Want to do something with every line? Then String::lines is the right choice:

var multiline = "This\r\nis a\r\nmultiline\r\nstring";
multiline.lines()
	// we now have a `Stream<String>`
	.map(line -> "// " + line)
	.forEach(System.out::println);

// OUTPUT:
// This
// is a
// multiline
// string

Note that the string uses Windows' \r\n and even though I'm on Linux, lines() still splits it. That's because regardless of the operating system, the method treats \r, \n, and \r\n as line terminators and splits there - even if they are mixed in the same string.

The streamed lines never contain the line terminator itself. They can be empty ("like\n\nin this\n\ncase", which has 5 lines), but the line at the end of the string will be ignored if its empty ("like\nhere\n"; 2 lines).

Unlike split("\R"), lines() is lazy and, I quote, "provides better performance [...] by faster search of new line terminators". (If someone feels like firing up JMH to verify this, let me know.) It's also much better at conveying what you want to do and returns a more convenient data structure (stream instead of array). Neat.

Stripping Whitespace With String::strip

Since forever, String offered trim to remove whitespace, which it considered everything with a Unicode up to U+0020. Yep, BACKSPACE (U+0008) is whitespace and so is BELL (U+0007) but LINE SEPARATOR (U+2028) isn't. 🤔

Java 11 introduces strip, which has a little more nuanced approach. It uses Java 5's Character::isWhitespace to determine what to strip. From its Javadoc that is:

  • SPACE_SEPARATOR, LINE_SEPARATOR, PARAGRAPH_SEPARATOR, but not non-breaking space
  • HORIZONTAL TABULATION (U+0009), LINE FEED (U+000A), VERTICAL TABULATION (U+000B), FORM FEED (U+000C), CARRIAGE RETURN (U+000D)
  • FILE SEPARATOR (U+001C), GROUP SEPARATOR (U+001D), RECORD SEPARATOR (U+001E), UNIT SEPARATOR (U+001F)

Building on this logic, there are two more stripping methods, stripLeading and stripTailing, which do what you'd expect.

Finally, if you just need to know whether a string would be empty after stripping whitespace, no need to actually do it - use isBlank instead:

" ".isBlank()); // space ~> true
" ".isBlank()); // non-breaking space ~> false

Repeating Strings with String::repeat

Life hack:

Step 1: Obsessively observe JDK development.

Step 2: Scour StackOverflow for related questions.

Step 3: Swoop in with new answer based on upcoming changes.

Step 4: ¯\_(ツ)_/¯

Step 5:

As you can see, String now has a method repeat(int). It behaves exactly according to expectations and there aren't any nooks and crannies to discuss.

Creating Paths With Path::of

I really like the Path API but going back and forth between paths as Path, File, URL, URI, and String, is really annoying. This has gotten a tiny bit less confusing in Java 11 by copying the two Paths::get methods to Path::of:

Path tmp = Path.of("/home/nipa", "tmp");
Path codefx = Path.of(URI.create("https://nipafx.dev"));

They can be deemed to be the canonical choice as both Paths::get methods forward to them.

Reading From And Writing To Files With Files::readString and Files::writeString

If I need to read from a large file, I usually use Files::lines to get a lazy stream of its content. Likewise, for writing a lot of content that may not be present in memory all at once, I use Files::write by passing it an Iterable<String>.

But what about the easy case where I can handle the entire content as a simple string? That hasn't been terribly convenient because Files::readAllBytes and the matching overload for Files::write operate with byte arrays. 🤢

Here's where Java 11 interjects by adding readString and writeString to Files:

String haiku = Files.readString(Path.of("haiku.txt"));
String modified = modify(haiku);
Files.writeString(Path.of("haiku-mod.txt"), modified);

Straightforward and simple to use. If need be, you can also pass a CharSet to readString and OpenOptions to writeString.

Null I/O With Reader::nullReader et al

Need an OutputStream that discards input bytes? Need an empty InputStream? What about Reader and Writer that do nothing? Java 11 has got you covered:

InputStream input = InputStream.nullInputStream();
OutputStream output = OutputStream.nullOutputStream();
Reader reader = Reader.nullReader();
Writer writer = Writer.nullWriter();

I wonder, though, is null really the best prefix here? I don't like how it's used to mean "intended absence"... Maybe noOp would have been better?

{ } ~> [ ] With Collection::toArray

How do you turn a collection into an array?

// before Java 11
List<String> list = /*...*/;
Object[] objects = list.toArray();
String[] strings_0 = list.toArray(new String[0]);
String[] strings_size = list.toArray(new String[list.size()]);

The first option, objects, looses all type information, so it's out. What about the other two? Both are cumbersome, but the first is more succinct. The latter creates an array with the required size, so it's more performanty (i.e. "appears more performant"; cf. truthy). But does it actually perform better? No, on the contrary, it's slower (at the moment).

But why should we care about that? Isn't there a better way to do this? In Java 11 there is:

String[] strings_fun = list.toArray(String[]::new);

There's a new overload of Collection::toArray that takes an IntFunction<T[]>, i.e. a function that accepts the length of the array to produce as input and returns an array of that size. That can be expressed succinctly as a constructor reference T[]::new (for concrete T).

Fun fact, the default implementation of toArray(IntFunction<T[]>) always passes 0 to the provided array generator. At first, I thought that decision was made based on the better performance of starting out with such a 0-length array, but now I think it may be because, for some collections, computing the size can be very expensive and so it wouldn't be a good default implementation on Collection. Concrete collections like ArrayList could then override, but, in Java 11, they don't. Not worth it, I guess.

This new method supersedes toArray(T[]) unless you already have an array lying around, in which case the old method remains useful.

Not Present With Optional::isEmpty

When you use Optional a lot, particularly in large code bases where you interact with a lot of non-Optional-bearing code, you'll frequently have to check whether the value is present. Optional::isPresent is there for you. But about just as often, you want to know whether the Optional is empty. No problem, just use !opt.isPresent(), right?

Sure, that works, but it's almost always easier to understand an if if the condition is not negated. And sometimes, the Optional pops up at the end of a longer call chain and if you want to know whether it's empty, you need to put the ! all the way to the front:

public boolean needsToCompleteAddress(User user) {
	return !getAddressRepository()
		.findAddressFor(user)
		.map(this::canonicalize)
		.filter(Address::isComplete)
		.isPresent();
}

The ! is easy to miss. From Java 11 on, there's a better option:

public boolean needsToCompleteAddress(User user) {
	return getAddressRepository()
		.findAddressFor(user)
		.map(this::canonicalize)
		.filter(Address::isComplete)
		.isEmpty();
}

Inverting Predicates With Predicate::not

Talking about "not"... The Predicate interface has an instance method negate; it returns a new predicate that executes the same test but inverts the result. Unfortunately, I very rarely get to use it...

// want to print non-blank strings
Stream.of("a", "b", "", "c")
	// ugh, lambda ~> want to use method reference and negate it
	.filter(string -> !string.isBlank())
	// compiler has no target for method reference ~> error
	.filter((String::isBlank).negate())
	// ugh, cast ~> this is way worse than lambda
	.filter(((Predicate<String>) String::isBlank).negate())
	.forEach(System.out::println);

The problem is that I rarely have a Predicate instance at hand. Much more often, I want to create one with a method reference (and then invert it), but for that to work the compiler needs to know the target - without it, it doesn't know what to turn the reference into. And that's what happens when you attach a method call as in (String::isBlank).negate(): There's no longer a target for String::isBlank and so the compiler barfs. A properly placed cast fixes that but at what cost?

There's an easy solution, though. Don't use the instance method negate, use Java 11's new static method Predicate.not(Predicate<T>) instead:

Stream.of("a", "b", "", "c")
	// statically import `Predicate.not`
	.filter(not(String::isBlank))
	.forEach(System.out::println);

I like it!

Regular Expressions As Predicate With Pattern::asMatchPredicate

Have a regular expression? Want to filter based on it? What about this:

Pattern nonWordCharacter = Pattern.compile("\\W");
Stream.of("Metallica", "Motörhead")
	.filter(nonWordCharacter.asPredicate())
	.forEach(System.out::println);

I was really happy to discover this method! This Java 8 method, I should add. Oops, missed that one. 😂 Java 11 adds another such method: Pattern::asMatchPredicate. What's the difference?

  • asPredicate checks whether the string or any substring matches this pattern (it behaves like s -> this.matcher(s).find())
  • asMatchPredicate is only content if the entire string matches this pattern (it behaves like s -> this.matcher(s).matches())

Say you have a regular expression verifying phone numbers, but it doesn't contain ^ and $ to mark begin and end of line. Then the following may not do what you want it to:

prospectivePhoneNumbers.stream()
	.filter(phoneNumberPattern.asPredicate())
	.forEach(this::robocall);

Did you spot the error? A string like "y u want numberz? +1-202-456-1414" would pass the filter because it contains a valid phone number. Pattern::asMatchPredicate, on the other hand, would not have let it pass because the string, in its entirety, doesn't match the pattern.

Reflection

Here are all eleven-something gems at a glance - see if you still remember what each of them does. If you do, you passed. 😉

  • on String:
    • Stream<String> lines()
    • String strip()
    • String stripLeading()
    • String stripTrailing()
    • boolean isBlank()
    • String repeat(int)
  • on Path:
    • static Path of(String, String...)
    • static Path of(URI)
  • on Files:
    • String readString(Path) throws IOException
    • Path writeString(Path, CharSequence, OpenOption...) throws IOException
    • Path writeString(Path, CharSequence, Charset, OpenOption...) throws IOException
  • on InputStream: static InputStream nullInputStream()
  • on OutputStream: static OutputStream nullOutputStream()
  • on Reader: static Reader nullReader()
  • on Writer: static Writer nullWriter()
  • on Collection: T[] toArray(IntFunction<T[]>)
  • on Optional: boolean isEmpty()
  • on Predicate: static Predicate<T> not(Predicate<T>)
  • on Pattern: Predicate<String> asMatchPredicate()

And that's beyond the reactive HTTP/2 API and single-source-file execution. Have fun with Java 11!