Tuesday, May 14, 2019

Java Text Blocks

In the 13 May 2019 post "RFR: Multi-line String Literal (Preview) JEP [EG Draft]" on the OpenJDK amber-spec-experts mailing list, Jim Laskey announced a draft feature JEP named "Text Blocks (Preview)" (JDK-8222530).

Laskey's post opens with (I've added the links), "After some significant tweaks, reopening the JEP for review" and he is referring to the draft JEP that was started after the closing/withdrawing of JEP 326 ["Raw String Literals (Preview)"] (JDK-8196004). Laskey explains the most recent change to the draft JEP, "The most significant change is the renaming to Text Blocks (I'm sure it will devolve over time Text Literals or just Texts.) This is primarily to reflect the two-dimensionality of the new literal, whereas String literals are one-dimensional." This post-"raw string literals" draft JEP previously referred to "multi-line string literals" and now refers to "text blocks."

The draft JEP "Text Blocks (Preview)" provides detailed overview of the proposed preview feature. Its "Summary" section states:

Add text blocks to the Java language. A text block is a multi-line string literal that avoids the need for most escape sequences, automatically formats the string in predictable ways, and gives the developer control over format when desired. This will be a preview language feature.

This is a follow-on effort to explorations begun in JEP 326, Raw String Literals (Preview).

The draft JEP currently lists three "Goals" of the JEP and I've reproduced the first two here:

  1. "Simplify the task of writing Java programs by making it easy to express strings that span several lines of source code, while avoiding escape sequences in common cases."
  2. "Enhance the readability of strings in Java programs that denote code written in non-Java languages."

The "Non-Goals" of this draft JEP are also interesting and the two current non-goals are reproduced here:

  1. "It is not a goal to define a new reference type (distinct from java.lang.String) for the strings expressed by any new construct."
  2. "It is not a goal to define new operators (distinct from +) that take String operands."

The current "Description" of the draft JEP states:

A text block is a new kind of literal in the Java language. It may be used to denote a string anywhere that a string literal may be used, but offers greater expressiveness and less accidental complexity.

A text block consists of zero or more content characters, enclosed by opening and closing delimiters.

The draft JEP describes use of "fat delimiters" ("three double quote characters": ===) in the opening delimiter and closing delimiter that mark the beginning and ending of a "text block." As currently proposed, the text block actually begins on the line following the line terminator of the line with the opening delimiter (which might include spaces). The content of the text block ends with the final character before the closing delimiter.

The draft JEP describes "text block" treatment of some special characters. It states, 'The content may include " characters directly, unlike the characters in a string literal.' It also states that \" and \n are "permitted, but not necessary or recommended" in a text block. There is a section of this draft JEP that shows examples of "ill-formed text blocks."

There are numerous implementation details covered in the draft JEP. These include "compile-time processing" of line terminators ("normalized" to "to LF (\u000A)"), incidental white space (differentiation of "incidental white space from essential white space" and use of String::indent for custom indentation management), and escape sequences ("any escape sequences in the content are interpreted" per Java Language Specification and use of String::translateEscapes for custom escape processing).

Newly named "Java Text Blocks" look well-suited for the stated goals and the current proposal is the result of significant engineering effort. The draft JEP is approachable and worth reading for many details I did not cover here. Because this is still a draft JEP, it has not been proposed as a candidate JEP yet and has not been targeted to any specific Java release.

Monday, April 29, 2019

A New Era for Determining Equivalence in Java?

Liam Miller-Cushon has published a document simply called "Equivalence" in which he proposes "to create a library solution to help produce readable, correct, and performant implementations of equals() and hashCode()." In this post, I summarize some reasons why I believe this proposal is worth reading for most Java developers even if the proposal never gets implemented and why the proposal's implementation would benefit all Java developers if realized.

Miller-Cushon opens his proposal with a single-sentence paragraph: "Correctly implementing equals() and hashCode() requires too much ceremony." The proposal points out that today's powerful Java IDEs do a nice job of generating these methods, but that there is still code to be read and maintained. The proposal also mentions that "over time these methods become a place for bugs to hide." I have been on the wrong end more than once of particularly insidious bugs caused by an error in one of these methods and these can be tricky to detect.

All three editions of "Effective Java" provide detailed explanation and examples for how to write effective implementations of these methods, but it's still easy to get them wrong. The JDK 7 (Project Coin)-introduced methods Objects.equals(Object, Object) and Objects.hash(Object...) have helped considerably (especially in terms of readability and dealing with nulls properly), but there are still errors made in implementations of Object.equals(Object) and Object.hashCode().

Even if this "Equivalence" proposal never comes to fruition, there is some value in reading Miller-Cushon's document. One obvious benefit of this document is its capturing of "Examples of bugs in equals and hashCode implementations." There are currently nine bullets in this section describing the "wide array of bugs in implementations of equals and hashCode methods" that were often identified only when "static analysis to prevent these issues" was performed. These examples serve as a good reminder of the things to be careful about when writing implementations of these methods and also reminds us of the value of static analysis (note that Miller-Cushon is behind the static analysis tool error-prone).

Reading of the "Equivalence" document can also be enlightening for those wanting to better understand the related issues one should think about when developing the equivalence concept in Java. Through sets of questions in the "Requirements" and "Design Questions" sections, the document considers trade-offs and implementation choices that would need to be made. These cover topics such as how to handle nulls, instanceof versus getClass(), and the relationship to Comparator. Many of these considerations should probably be made today by Java developers implementing or maintaining their own implementations of equals(Object) and hashCode().

The "Related reading" section of the "Equivalence" document provides links to some interesting reading that includes the 2009 classic article "How to Write an Equality Method in Java" and Rémi Forax's ObjectSupport class (which delegates to ObjectSupports in some cases).

The "Equivalence" proposal was presented on the OpenJDK amber-spec-experts mailing list in a post title "A library for implementing equals and hashCode" and some of the feedback on that mailing list has led to updates to the document. One particularly interesting sentence for me in this discussion is Brian Goetz's statement, "That people routinely implement equals/hashCode explicitly is something we would like to put in the past." That seems like a welcome change!

Saturday, April 27, 2019

Two JEPs Proposed for JDK 13: Enhancing AppCDS and ZGC

Two JDK Enhancement Proposals (JEPs) were proposed for JDK 13 this week on the OpenJDK jdk-dev mailing list. Mark Reinhold posted these proposals in messages with titles that indicate the JEP topic: "JEP proposed to target JDK 13: 350: Dynamic CDS Archives" and "JEP proposed to target JDK 13: 351: ZGC: Uncommit Unused Memory".

The "Summary" of proposed JEP 350 ["Dynamic CDS Archives"] states, "Extend application class-data sharing to allow the dynamic archiving of classes at the end of Java application execution. The archived classes will include all loaded application classes and library classes that are not present in the default, base-layer CDS archive." JEP 310 introduced "Application Class-Data Sharing" (AKA "AppCDS") via JDK-8185996 and in conjunction with JDK 10.

JEP 351 ["ZGC: Uncommit Unused Memory"]'s "Summary" section states simply, "Enhance ZGC to return unused heap memory to the operating system." The "Motivation" section adds more background details, "ZGC does not currently uncommit and return memory to the operating system, even when that memory has been unused for a long time. This behavior is not optimal for all types of applications and environments, especially those where memory footprint is a concern." "ZGC" refers to the "Z Garbage Collector" and more details regarding it can be found on the OpenJDK ZGC page and on the ZGC Wiki page. The main project page states, "The goal of this project is to create a scalable low latency garbage collector capable of handling heaps ranging from a few gigabytes to multi terabytes in size, with GC pause times not exceeding 10ms."

Both proposed JEPs will be officially targeted for JDK 13 next week if no objections are raised or if any raised objections are "satisfactorily answered."

Monday, April 22, 2019

OpenJDK on GitHub

Project Skara was created "to ... investigate alternative SCM and code review options for the JDK source code, including options based upon Git rather than Mercurial, and including options hosted by third parties." The OpenJDK skara-dev mailing list included a post from Robin Westberg last week that announced, "We have added some additional read-only mirrors of a few different OpenJDK project repositories to the https://github.com/openjdk group..."

The read only OpenJDK repositories on GitHub will likely be more convenient for developers wanting to take advantage of the "open source" nature of OpenJDK to take a peek at its internals. More developers are likely to be comfortable with Git than with Mercurial. The GitHub-hosted repositories make it even easier to clone a given repository or to even fork it.

As of this writing, there are currently nine public repositories hosted on the OpenJDK GitHub site:

This is not the first time the OpenJDK has been mirrored on GitHub. There are 11 repositories in "Mirror of OpenJDK repositories": jdk (2017), jdk7u-jdk (2012), jdk7u (2012), openjdk-mirror-meta (2015), corba (2015), jaxp (2015), jdk7u-langtools (2012), jdk7u-jaxws (2012), jdk7u-jaxp (2012), jdk7u-hotspot (2012), and jdk7u-corba (2012). There is also a Project-Skara/jdk that was last updated in August 2018.

Project Skara is not finished and active development of OpenJDK continues on the Mercurial-based version control system. However, the availability of important OpenJDK repositories on GitHub should make it more convenient for Java developers to analyze OpenJDK source code.

Monday, April 15, 2019

April 2019 Update on Java Records

After Project Valhalla's "Value Types/Objects", the language feature I am perhaps the most excited to see come to Java is Project Amber's "Data Classes" (AKA "Records"). I wrote the post "Updates on Records (Data Classes for Java)" about this time last year and use this post to provide an update on my understanding of where the "records" proposal is now.

A good starting point for the current state of the "records" design work is Brian Goetz's February 2019 version of "Data Classes and Sealed Types for Java." In addition to providing background on the usefulness of "plain data carriers" being implemented with less overhead than with traditional Java classes and summarizing design decisions related to achieving that goal, this post also introduces noted Java developer personas Algebraic Annie, Boilerplate Billy, JavaBean Jerry, POJO Patty, Tuple Tommy, and Values Victor.

Here are some key observations that Goetz makes in the "Data Classes and Sealed Types for Java" document.

  • "Java asks all classes ... to pay equally for the cost of encapsulation -- but not all classes benefit equally from it."
  • Because "the cost of establishing and defending these boundaries ... is constant across classes, but the benefit is not, the cost may sometimes be out of line with the benefit."
  • "This is what Java developers mean by too much ceremony' -- not that the ceremony has no value, but that they're forced to invoke it even when it does not offer sufficient value."
  • "The encapsulation model that Java provides -- where the representation is entirely decoupled from construction, state access, and equality -- is just more than many classes need."
  • "... we prefer to start with a semantic goal: modeling data as data."
  • "The API for a data class models the state, the whole state, and nothing but the state. One consequence of this is that data classes are transparent; they give up their data freely to all requestors."
  • "We propose to surface data classes in the form of records; like an enum, a record is a restricted form of class. It declares its representation, and commits to an API that matches that representation. We pair this with another abstraction, sealed types, which can assert control over which other types may be its subclasses."
  • "Records use the same tactic as enums for aligning the boilerplate-to-information ratio: offer a constrained version of a more general feature that enables standard members to be derived. ... For records, we make a similar trade; we give up the flexibility to decouple the classes API from its state description, in return for getting a highly streamlined declaration (and more)."
  • Restrictions on currently proposed records include: "record fields cannot be mutable; no fields other than those in the state description are permitted; and records cannot extend other types or be extended."
  • "... an approach that is focused exclusively on boilerplate reduction for arbitrary code is guaranteed to merely create a new kind of boilerplate."
  • "...records are not intended to replace JavaBeans, or other mutable aggregates..."

One section of Goetz's post provides an overview of likely use cases for records. These usage cases (which include descriptions in the Goetz post) include multiple return values (something that Java developers seem to frequently use custom or library-provided tuples for), Data Transfer Objects (DTOs), compound map keys, messages, and value wrappers.

Goetz specifically addresses the question related to the records proposal, "Why not 'just' do tuples?" Goetz answers his own question with multiple reasons for using the data class/record concept rather than simply adding tuples to Java. I'm generally not a fan of tuples because I think they reduce the readability of Java code and, especially if the values in the tuple have the same data type, can lead to subtle errors. Goetz articulates similar thinking, "Classes and class members have meaningful names; tuples and tuple components do not. A central aspect of Java's philosophy is that names matter; a Person with properties firstName and lastName is clearer and safer than a tuple of String and String." I prefer getFirstName() and getLastName() to getLeft() and getRight() or to getX() and getY(), so this resonates with me.

Another section of the Goetz document that I want to emphasize is the section headlined "Are records the same as value types?" This section compares Project Valhalla's value types to Project Amber's data classes. Goetz writes, "Value types are primarily about enabling flat and dense layout of objects in memory. In exchange for giving up object identity ..., the runtime gains the ability to optimize the heap layout and calling conventions for values. With records, in exchange for giving up the ability to decouple a classes API from its representation, we gain a number of notational and semantic benefits. ... some values may still benefit from state encapsulation, and some records may still benefit from identity, so they are not the exact same trade."

There has been more discussion on the amber-spec-experts mailing list this week regarding what to call "data classes." Naming is important and notoriously difficult in software development and I appreciate this discussion because the arguments for various names have helped me to understand what the current thinking is about what "data classes" are currently envisioned to be and not to be. Here are some excerpts from this enlightening thread:

  • Rémi Forax likes "named tuples" because data classes are immutable and have some commonality with "nominal tuples."
  • Brian Goetz likes starting with "records are just nominal tuples" to "avoid picking that fight" between different groups of people with "two categories of preconceived notions" of what a tuple is.
  • Kevin Bourrillion adds, "Records have semantics, which makes them 'worlds' different from tuples. ... I think it's fair to say that all a record 'holds' is a 'tuple', but it's so much more. Record is to tuple as enum is to int."
  • Guy Steele adds, "Java `record` is to C `struct` as Java `enum` is to C `enum`."

I continue looking forward to getting "data classes" in Java at some point in the future and appreciate the effort being put into ensuring their successful adoption when added. When transitioning from C++ to Java, I missed the enum greatly, but the wait was worth it when Java introduced its own (more powerful and safer) enum. I hope for a similar feeling about Java data classes/records when we get to start using them.

Saturday, April 13, 2019

Viewing TLS Configuration with JDK 13

JDK 13 Early Access Build 16 is now available and one of the interesting additions it brings is the ability to have the keytool command-line tool display the current system's TLS configuration information. This is easier than trying to find supported TLS information in separate documentation and match that information to one's JDK vendor and version.

To see the TLS configuration details with JDK 13 Early Access Build 16, one simply needs to enter keytool -showinfo -tls on the command line, but I'll describe a few more things about this command in this post.

The next screen snapshot shows that the JDK I'm using for my examples is the JDK 13 Early Access Build 16 and demonstrates that the keytool usage now shows the tool including the -showinfo command.

Simply entering keytool without any commands or options results in the usage statement shown in the screen snapshot. The description for the -showinfo command is, "Displays security related information."

The next screen snapshot demonstrates the hint that is provided when one tries to use keytool -showinfo without an option ('Try "keytool -showinfo -tls".'). The image also shows the options associated with the keytool command -showinfo that are displayed when keytool -showinfo --help is entered.

The --help option used with the -showinfo command displays a -v option, but I found on my Windows installation that this -v option does not provide any additional value over simply using the -tls option. The next screen snapshot shows the results of attempting to use the -v option alone (without the -tls option):

When trying to use -v along with the keytool command -showinfo, we get an error message and a recommendation to try keytool -showinfo -tls instead. That does indeed work better as shown in the next screen snapshot that only shows partial results of what's returned.

The output from running keytool -showinfo -tls lists "Enabled Protocols" and "Enabled Cipher Suites." In this case, we see that the "enabled protocols" are TLSv1.3, TLSv1.2, TLSv1.1, and TLSv1.

I found it interesting to look at the code changes required to implement this new command and option for keytool. The implementation uses the JDK's javax.net.ssl.SSLContext class's getDefault() method to acquire the "default SSL context." The returned SSLContext instance's getSocketFactory() method is invoked and the createSocket() method is called on the returned instance of javax.net.ssl.SSLSocketFactory. The returned instance of javax.net.ssl.SSLSocket has two methods getEnabledProtocols() and getEnabledCipherSuites() that return the values shown above in the output from running keytool -showinfo -tls.

The addition to JDK 13's keytool command-line tool of the -showinfo command with its -tls option is available as of Early Access Build 16 and was delivered via JDK-8219861. It's also worth noting that JDK-8204636 may eventually lead to improvements for JDK's TLS 1.3 support.

Thursday, April 4, 2019

New Valhalla Developments: Forwarders and Poxes

In the post "Updated VM-bridges document" on the valhalla-spec-experts OpenJDK mailing list, Brian Goetz provides, "an updated doc on forwarding-reversing bridges in the VM." This was a topic at the late March 2019 Burlington (Massachusetts) Valhalla Offsite. A nice summary of Burlington Valhalla Offsite is provided by Karen Kinnear, in which she records that Goetz discussed "migration" using "field bridges" and "method bridges" as part of L20 phase of the new Valhalla phasing approach. Goetz's post adds significant background details. In this post, I reference a few of the more interesting points made in these posts, but both posts provide significantly more details than I'm providing here and are worth reading for anyone interested in the direction of Project Valhalla.

Forwarders

Although Goetz spends the majority of his post discussing the "migration aspects" of "forwarding+reversing bridges" and the of "forwarders," I found the historical background he provided in the introductory paragraphs to be a clean, concise summary of how we got to where we are now. Here are some brief statements from this historical section of the post that stood out to me (quotes here are very slightly modified for emphasis and to provide links):

  • "In the Java 1.0 days, `javac` was little more than an 'assembler' for the classfile format, translating source code to bytecode in a mostly 1:1 manner."
  • "Over time, we've seen small divergences between the language model and the classfile model, and each of these is a source of sharp edges."
  • "In Java 1.1 the addition of inner classes, and the mismatch between the accessibility model in the language and the JVM (the language treated a nest as a single entity; the JVM treat nest members as separate classes) required "access bridges" (`access$000` methods), which have been the source of various issues over the years."
  • "Twenty years later, these methods were obviated by Nest-based Access Control [jep181] -- which represents the choice to align the VM model to the language model"
  • "In Java 5, while we were able to keep the translation largely stable and transparent through the use of erasure..."
  • "... there was one point of misalignment; several situations ... could give rise to the situation where two or more method descriptors -- which the JVM treats as distinct methods -- are treated by the language as if they correspond to the same method."
  • "To fool the VM, the compiler emits "bridge methods" which forward invocations from one signature to another. And, as often happens when we try to fool the VM, it ultimately has its revenge."
  • "Java 5 introduced the ability to override a method but to provide a more specific return type. (Java 8 later extended this to bridges in interfaces as well.)"

After providing the historical background and summarizing the issues associated with these historical developments, Goetz moves onto to providing significantly more details about "bridge methods." He discusses the "anatomy of a bridge method" (forwarding/invokevirtual) and points out that "bridges are brittle" because "separate compilation can move bridges from where already-compiled code expects them to be to places it does not expect them." I particularly like Goetz's concise summary of the fundamental brittleness problem with bridge methods:

The basic problem with bridge methods is that the language views the two method descriptors as two faces of the same actual method, whereas the JVM sees them as distinct methods. (And, reflection also has to participate in the charade.)

Goetz discusses in his post the "limits of bridges" and points out that their limits are mostly encountered when attempting to migrate in a binary compatible fashion:

The problem of migration arises both from language evolution (Valhalla aims to enable compatible migrating from value-based classes to value types, and from erased generics to specialized), as well as from the ordinary evolution of libraries.

After providing some specific examples of where bridge methods fail migration attempts for fields and overridden methods, Goetz introduces "fowarders":

In this document, we attempt to learn from the history of bridges, and create a new mechanism -- "forwarders" -- that work with the JVM instead of against it. This raises the level of expressivity of classfiles and opens the possibility of greater laziness. It is possible that traditional bridging scenarios can eventually be handled by forwarders too...

Goetz devotes the remainder of the post to specifics of forwarders such as their invocation, using forwarders with fields, overriding forwarders, adaptation of forwarders, and type checking and corner cases associated with forwarders.

Poxes: Value Type Wrappers for Primitives

There was more discussed in the Valhalla Offsite than forwarding. One of Kinnear's notes that stood out to me stated, "Poxes: value type wrappers for primitives." Since the introduction of "autoboxing and unboxing" in JDK 1.5, Java developers have become comfortable with the conceptual relationship of "boxed"/"wrapper" reference types to their primitive counterparts. Therefore, it does make some sense to me to use similar terminology for the concept of value type "wrappers" of primitive counterparts being called "poxes." Goetz describes "poxing" in the post "Finding the spirit of L-World" and calls "creating a value box for primitives" "a 'pox'" and discusses the possibility of "adjust[ing] the compiler's boxing behavior (when boxing to `Object` or an interface)" to "prefer the pox to the box."

Updating Valhalla Value Types Phases

In her post "Updated phasing of Valhalla Value Types," Kinnear outlines Valhalla value types "Phases/AIs for Proposals" discussed at the Burlington Valhalla offsite meeting mentioned earlier. She calls out one major timing proposal in its own paragraph (I added the emphasis):

One important phasing is moving null-default value types and migrating value-based-classes to null-default value types was moved out to L20, which is after the initial preview.

Kinnear provides a list of "phases/APIs" categorized under one of three general milestone allocations: L10, L20, and L100. The two specific topics covered in my post (forwarding and poxes) appear to be currently proposed for L20.

Conclusion

Valhalla's value types are still a ways out before we'll see them in a General Availability JDK, but I appreciate the significant effort being invested in these value types and look forward to benefiting from them at some point in the future.

Saturday, March 23, 2019

Better Default NullPointerException Messages Coming to Java?

I have recently been interested in a February 2019 into March 2019 discussion on the OpenJDK core-libs-dev mailing list regarding addressing the lack of a detailed message associated with a NullPointerException that was thrown after being instantiated with its no-arguments constructor. This is an issue I've run into frequently when using Java and which has even caused me to change code in a few cases to better deal with the issue.

In many cases, NullPointerException (NPE) can be one of the easier exceptions to resolve (or at least diagnose what was null) if only one possible source of a NullPointerException exists in the statement and if line numbers are available in the stack trace (not compiled with -g:none).

Although it is especially challenging for those new to Java, a NullPointerException with no message can be a disappointing experience even for experienced Java developers in certain cases. The most obvious case when having no message associated with the NullPointerException is when there are multiple candidates in a given statement that might be throwing the NullPointerException. One example of this case is calling methods upon each previous method's return object in a manner such as this: getA().getB().getC()... where each of the methods potentially returns null. Another example is when multiple arguments of primitive data types to a method (or constructor) might lead to a NullPointerException if the caller passes a null to to that method that is dereferenced to be a primitive.

Enhancement JDK-8218628 ("Add detailed message to NullPointerException describing what is null.") addresses some of these cases. The description of this enhancement states, "When getting a NPE it is often hard to determine which reference in an expression had been null. This change adds a message telling this." This enhancement also provides several examples of Java statements that typically result in NullPointerException with potentially frustrating lack of detail. I have captured similar cases to these examples in the GitHub-hosted class NpeDemo (see this version to match to line numbers in output below). When those demonstration examples are executed (they all intentionally throw NPEs), the output appears as shown in the following when compiled with default settings (full stack information still available):

=========================================
| #1: Element [0] on null boolean array |
=========================================

java.lang.NullPointerException
 at dustin.examples.npe.NpeDemo.demonstrateFirstExampleIndexAccessOnNullBooleanArray(NpeDemo.java:37)
 at dustin.examples.npe.NpeDemo.demonstrateJdk8218628Examples(NpeDemo.java:179)
 at dustin.examples.npe.NpeDemo.main(NpeDemo.java:310)

=================================
| #2: .length on null boolean[] |
=================================

java.lang.NullPointerException
 at dustin.examples.npe.NpeDemo.demonstrateSecondExampleLengthOnNullBooleanArray(NpeDemo.java:59)
 at dustin.examples.npe.NpeDemo.demonstrateJdk8218628Examples(NpeDemo.java:180)
 at dustin.examples.npe.NpeDemo.main(NpeDemo.java:310)

=======================================
| #3: Assigning float to null float[] |
=======================================

java.lang.NullPointerException
 at dustin.examples.npe.NpeDemo.demonstrateThirdExampleAssigningValueToElementOfNullFloatArray(NpeDemo.java:80)
 at dustin.examples.npe.NpeDemo.demonstrateJdk8218628Examples(NpeDemo.java:181)
 at dustin.examples.npe.NpeDemo.main(NpeDemo.java:310)

======================================
| #4: Accessing field on null object |
======================================

java.lang.NullPointerException
 at dustin.examples.npe.NpeDemo.demonstrateFourthExampleAccessInstanceFieldOfNullObject(NpeDemo.java:101)
 at dustin.examples.npe.NpeDemo.demonstrateJdk8218628Examples(NpeDemo.java:182)
 at dustin.examples.npe.NpeDemo.main(NpeDemo.java:310)

===================
| #5: throw null; |
===================

java.lang.NullPointerException
 at dustin.examples.npe.NpeDemo.demonstrateFifthExampleThrowingConstantNull(NpeDemo.java:121)
 at dustin.examples.npe.NpeDemo.demonstrateJdk8218628Examples(NpeDemo.java:183)
 at dustin.examples.npe.NpeDemo.main(NpeDemo.java:310)

================================================
| #6: Method invocation on null instance field |
================================================

java.lang.NullPointerException
 at dustin.examples.npe.NpeDemo.demonstrateSixthExampleMethodInvocationOnNullInstanceField(NpeDemo.java:141)
 at dustin.examples.npe.NpeDemo.demonstrateJdk8218628Examples(NpeDemo.java:184)
 at dustin.examples.npe.NpeDemo.main(NpeDemo.java:310)

=============================================
| #7: synchronized() on null instance field |
=============================================

java.lang.NullPointerException
 at dustin.examples.npe.NpeDemo.demonstrateSeventhExampleSynchronizedNullInstanceField(NpeDemo.java:161)
 at dustin.examples.npe.NpeDemo.demonstrateJdk8218628Examples(NpeDemo.java:185)
 at dustin.examples.npe.NpeDemo.main(NpeDemo.java:310)

==========================================================================
| <<< Null Lost in Long Series of Method Invocations in Single Statement |
==========================================================================

java.lang.NullPointerException
 at dustin.examples.npe.NpeDemo.demonstrateNullLostInSeriesOfMethodInvocationsInSingleStatement(NpeDemo.java:198)
 at dustin.examples.npe.NpeDemo.main(NpeDemo.java:311)

=======================================================
| <<< Null Lost in Dereferenced Constructor Arguments |
=======================================================

java.lang.NullPointerException
 at dustin.examples.npe.NpeDemo.demonstrateNullLostInConstructorAcceptingMultiplePotentiallyNullArgumentsDereferenced(NpeDemo.java:226)
 at dustin.examples.npe.NpeDemo.main(NpeDemo.java:312)

==================================================
| <<< Null Lost in Dereferenced Method Arguments |
==================================================

java.lang.NullPointerException
 at dustin.examples.npe.NpeDemo.demonstrateNullLostInMethodAcceptingMultiplePotentiallyNullArgumentsDereferenced(NpeDemo.java:254)
 at dustin.examples.npe.NpeDemo.main(NpeDemo.java:313)

There is no message provided with any of the NullPointerExceptions shown in the examples above. However, in these cases, the culprit is relatively easy to identify because the methods they occur in are small and there are line numbers that point directly to where the NPE was thrown. These would be more difficult to identify if there were no line numbers (source compiled with -g:none) and the methods were long (multiple lines in which NPEs could be thrown) or there were overloaded versions of the method with the same name.

Had the code been compiled with -g:none, there would be no class name or line number shown in the stack traces [would just list (Unknown Source) instead of (file name:line number)] and it could be trickier to detect where the NPE was thrown, especially if thrown from a lengthy method with many candidates for NPEs or from a method that was overloaded multiple times in the same class such that method name alone is not as helpful.

Some of the examples demonstrated above feature NPEs that are difficult to identify even when one knows the line number because there are so many potential throwers of NPE on that line. Changes such as those proposed by JDK-8218628 would be most welcome in these cases.

Although a solution was implemented for JDK-8218628, it has since been decided that there are enough considerations involved to justify a JDK Enhancement Proposal (JEP) to work out more design and implementation details. This JEP is JDK-8220715 ("Add detailed message to NullPointerException describing what is null") and its "Summary" states, "NullPointerExceptions are freqently encountered developing or maintaining a Java application. NullPointerExceptions often don't contain a message. This complicates finding the cause of the exception. This JEP proposes to enhance the exception text to tell what was null and which action failed."

JEP JDK-8220715 also provides detailed description of the proposed basic algorithm for computing a message for NPE when one is not explicitly provided. The text points out that when a NullPointerException is thrown in its example, "the original Java code is not available," but the information is still "stored in the 'backtrace' field of an exception object" which is "a field private to the jvm implementation."

JEP JDK-8220715 highlights that "computing the NullPointerException message proposed here is a considerable overhead," but addresses that by proposing "delay computing the message until it is actually accessed." In other words, the "default" NPE message would only be calculated if an explicit one had not been provided when the NullPointerException was instantiated.

The "Alternatives" section of JEP JDK-8220715 states that "the current proposal is to implement this in the Java runtime in C++ accessing directly the available datastructures in the metaspace." The section considers some alternatives to this approach (such as implementing it via a JDK library such as StackWalker) and explains why the proposed approach may be preferable to the alternatives.

For more background details related to the proposed enhancements to NullPointerException messages, see the OpenJDK core-libs-dev mailing list. Here are some posts from that discussion that may be of interest with excerpt shown from each post:

  • Goetz Lindenmaier: "... since Java 5, our internal VM reports verbose null pointer exception messages. I would like to contribute this feature to OpenJDK. ... The messages are generated by parsing the bytecodes. For not to have any overhead when the NPE is allocated, the message is only generated when it is accessed by getMessage() or serialization. For this I added a field to NPE to indicate that the message still needs to be computed lazily."
  • Christoph Langer: "... thanks for bringing this into OpenJDK finally. I know of people that'll be quite happy about this feature."
  • Peter Levart: "Make sure to initialize the NPE_MESSAGE_PENDING to a new String("something") or else you may be sharing this constant reference with somebody else via string interning..."
  • Andrew Dinn: "Also, if you want your message to reflect the bytecode that is actually in use when the exception occurs then you really need to do it by pulling the bytecodes out of the method metadata. The bytecode returned by JvmtiClassFileReconstitutor will not include any bytecode changes that were installed by a ClassFileTransformer. However, this is a potential can of worms because old and new versions of a method and associated bytecode can exist at the same time. You need to be sure which version of the method and, hence, bytecode the exception was generated from. If you are trying to do this from Java by calling into the JVM then I think you are going to have problems."
  • Goetz Lindenmaier: "The original implementation is C++ and walks the metaspace given the method* and BCI where the exception occurred. So it uses only data already sitting in memory. See JVM_GetExtendedNPEMessage() in jvm.cpp. The idea was to implement this in Java using StackWalker and ASM. If I had the right bytecodes, and the right starting point, ASM would be helpful to implement the analysis I think."
  • Mandy Chung: "We all think that improving NPE message is a useful enhancement for the platform and helps developers to tell what causes NPE. ... This would get the discussion on the proposal feature and then the discussion of the best way to to implement it in the VM, library, or combination."
  • Maurizio Cimadamore: "... this enhancement will be a great addition to our platform ... I also think that the design space for such an enhancement is non trivial, and would best be explored (and captured!) in a medium that is something other than a patch."
  • Goetz Lindenmaier: "... better wording of the messages ... Especially look at the first few messages, they point out the usefulness of this change. They precisely say what was null in a chain of dereferences."
  • Maurizio Cimadamore: "... please find the attached ASM-based patch. It is just a PoC, as such it does not provide as fine-grained messages as the one discussed in the RFE/JEP, but can be enhanced to cover custom debugging attribute ..."

There are numerous other posts in the threads and the posts above are samples of the discussion.

Having better "default" information associated with NPEs will be a welcome addition. JDK-8218628 is currently associated with JDK 13, but now that JDK-8220715 exists, it may be a bit less certain whether this will be associated with JDK 13. A draft JEP has been written for this, but as a draft JEP, it is not yet targeted to a particular JDK release.

Tuesday, March 19, 2019

Java 12 General Availability

Mark Reinhold announced today that "JDK 12, the reference implementation of Java 12, is now Generally Available." In that announcement, Reinhold stated that "GPL-licensed OpenJDK builds from Oracle are available" at https://jdk.java.net/12 and that "builds from other implementors will no doubt be available soon." Reinhold's post also summarized the eight features delivered with JDK 12:

  • 189: Shenandoah: A Low-Pause-Time Garbage Collector (Experimental)
  • 230: Microbenchmark Suite
  • 325: Switch Expressions (Preview)
  • 334: JVM Constants API
  • 340: One AArch64 Port, Not Two
  • 341: Default CDS Archives
  • 344: Abortable Mixed Collections for G1
  • 346: Promptly Return Unused Committed Memory from G1

There have been, of course, several other blog posts and online resources announcing the JDK 12 release. Some of the most significant are:

Here are some of my previous posts related to JDK 12:

Reinhold completed his "Java 12 / JDK 12: General Availability" message, "Coming up next ... lucky 13!" In a slightly later message on that same mailing list, Reinhold provided the "Proposed schedule for JDK 13":

2019/06/13Rampdown Phase One
2019/07/18Rampdown Phase Two
2019/08/08Initial Release Candidate
2019/08/22Final Release Candidate
2019/09/17General Availability

Tuesday, February 19, 2019

Stashing Previously Set psql Variables

The command-line based "PostgreSQL interactive terminal" known as psql is handy for manipulating and accessing data in a PostgreSQL database. Because of its command-line nature, psql is particularly well suited for use in scripts. One of the psql features that makes it even more useful in scripting contexts is its support for "meta-commands". As the psql documentation states, "Anything you enter in psql that begins with an unquoted backslash is a psql meta-command that" and "these commands make psql more useful for administration or scripting."

When writing psql scripts, it is often preferable to set some variables locally for the time period the script is being run, but might also be desirable to not change these variables permanently for the psql session if it's likely that other scripts or other work will be performed from the psql session after the script's conclusion. In this post, I will demonstrate use of psql's \set meta-command to temporarily store off previous settings of variables to restore these settings at the script's conclusion.

The psql documentation describes "a number of ... variables [that] are treated specially by psql." These "specially treated variables" are the ones that we most likely want to ensure that we set for our script's duration only and the restore their previously set values upon script exit. The documentation describes these "specially treated variables": "They represent certain option settings that can be changed at run time by altering the value of the variable, or in some cases represent changeable state of psql. By convention, all specially treated variables' names consist of all upper-case ASCII letters (and possibly digits and underscores). To ensure maximum compatibility in the future, avoid using such variable names for your own purposes." Examples of these "specially treated variables" include AUTOCOMMIT, ECHO, ECHO_HIDDEN, PROMPT1, PROMPT2, PROMPT3, and VERBOSE, but there are many more.

For demonstration purposes, let's suppose you want to set the ECHO variable to something other than its default (none). For our purposes, we'll set ECHO to queries. We want to make sure, however, that we set it back to whatever it was when our script was called before leaving the script. The following simple psql logic accomplishes this.

\set PRIOR_ECHO :ECHO
\set ECHO queries

-- Run various queries for which you want to see the query itself output before the query results ...

\set ECHO :PRIOR_ECHO

It's as simple as that to temporarily set "specially treated variables" for your script's convenience without permanently changing the settings for the caller who might be running your script in the same psql session. The key things to remember are that the \set meta-command is always all lowercase, the specially treated variables have names that are always all uppercase but the specially treated variable values do not need to be uppercase (and typically are not), and the values in a variable can be accessed by prefixing the variable name with a colon (:).

Friday, February 15, 2019

PostgreSQL's psql \set versus SET

It is easy for someone who is new to PostgreSQL and who uses PostgreSQL's terminal editor psql to confuse the commands \set and SET. This post contrasts these commands and provides a brief overview of other commands that include the word "set".

The easiest way to remember how to differentiate \set from SET is to keep in mind that the "backslash commands" such as \set are "meta commands" for the command-line psql tool and do not mean anything to the PostgreSQL database itself. The SET command, which lacks a backslash, is a PostgreSQL database command that happens to be executed against the database from the psql command-line client.

Contrasting \set and SET

Command \set SET or set
(or other case-insensitive variation1)
Context psql terminal editor configuration meta command
(interactive client terminal configuration)
PostgreSQL database configuration command
(server configuration)
Ends with Semicolon? No Yes
Command Case Sensitive? Yes
(must be exactly \set)
No
Parameter/Variable Case Sensitive? Yes2 No
How are Settings Displayed? \set3 SHOW ALL; or show all;4
\echo :variable_name SHOW variable_name;5
Examples \set AUTOCOMMIT on SET search_path TO myschema, public;
Footnotes
  1. I prefer to use all uppercase letters for SET to more clearly differentiate from /set.
  2. Two variables with same letters but different cases are two distinct variables.
  3. \set displays all variables when no arguments are provided to it.
  4. Any case variation (even sHoW aLl;) works.
  5. Any case variation of command and/or variable name also works.

There are two more psql meta commands that that "set" things and include the name "set". The \pset met command configures how psql presents "query result tables." Like \set, \pset can be specified without argument to see all of the current presentation settings.

Unlike the psql meta commands \set and \pset, the \gset psql metacommand does affect the PostgreSQL server because \gset submits the query buffer to the server and then stores the output returned from the server into specified psql variables. I discussed \gset with a few additional details in the blog post "Setting PostgreSQL psql Variable Based Upon Query Result."

Although \set and SET can be used to set variables, the easiest way to distinguish between them is to consider that the backslash commands such as \set are psql commands (and so \pset sets variables in the psql client tool) and commands without the backslash such as SET are PostgreSQL commands sent to the server from psql or from any other client (but ultimately set variables on the server).

Monday, February 4, 2019

jcmd, Circa JDK 11

Nicolas Fränkel recently published a survey of command-line tools delivered with OpenJDK 11 in the blog post "OpenJDK 11, tools of the trade." In that post, he briefly summarizes the tools jps (a JVM process status tool), jinfo (JVM configuration details), jmap (classes/objects on the heap), jstack (thread analysis), and graphical tool JConsole (monitor Java applications).

All of these tools are handy for Java developers to be aware of to apply as needed and Fränkel's post provides a nice introductory overview for those new to these tools. In recent years, I've moved toward applying the single jcmd tool instead of most of the other command-line tools (though it doesn't replace graphical tool JConsole in any way) as I've discussed in the post "jcmd: One JDK Command-Line Tool to Rule Them All."

There is a brief discussion on the related /r/java subreddit thread regarding jcmd versus the individual tools. I can see advantages to both approaches (using jcmd or using multiple individual tools). I contrast my perceptions of their relative advantages and disadvantages here.

jcmd Versus the Rest
jcmdOther Tools
Single interactive tool Different tools with varying names and options
More keystrokes/commands required to run functionality due to interactive nature Fewer keystrokes required for those familiar with commands and options and for cases where command/options being used are supported for the given JVM process
jcmd <pid> help provides the specific functions supported on that JVM process for jcmd analysis Results of running individual tool against JVM process is primary method of detecting that tool's support (or lack thereof) for that process
Supports only most commonly used subset of functionality of some of the individual tools Each tool, by its nature, sets the bar for supported functionality
Newer with fewer online resources Older with more online resources
Not considered "experimental" Several of the individual tools (jps, jinfo, jmap, jstack, and more) are labeled "experimental" and are subject to change/removal (Tools Reference states that "experimental tools are unsupported and should be used with that understanding. They may not be available in future JDK versions. Some of these tools aren’t currently available on Windows platforms.")
Significant jcmd provided-details are available programmatically via DiagnosticCommandMBean Direct corresponding programmatic access is rarely available for individual tools

Whether to use jcmd or one of the individual tools largely comes down to individual taste and preferences. Those who are already experienced with existing individual tools may prefer the more direct approach of those tools while those not familiar with the individual tools may prefer the interactive ability provided by jcmd for determining what tools and options are available. I certainly prefer non-experimental tools over "experimental" tools, but many of these tools have been labeled "experimental" for many versions of the JDK and are still with us.

The previously mentioned blog post "jcmd: One JDK Command-Line Tool to Rule Them All" describes how to use jcmd's interactive features to identify its capabilities supported for various JVM processes. There is a table toward the end of that post that "maps" jcmd options to some of the corresponding individual tools' commands and options. I reproduce that here for convenience.

FunctionalityjcmdSimilar Tool
Listing Java Processes jcmd jps -lm
Heap Dumps jcmd <pid> GC.heap_dump jmap -dump <pid>
Heap Usage Histogram jcmd <pid> GC.class_histogram jmap -histo <pid>
Thread Dump jcmd <pid> Thread.print jstack <pid>
List System Properties jcmd <pid> VM.system_properties jinfo -sysprops <pid>
List VM Flags jcmd <pid> VM.flags jinfo -flags <pid>

The jcmd tool continues to be enhanced. JDK 9 saw several enhancements to jcmd via JEP 228 ("Add More Diagnostic Commands"). In JDK 11, support for displaying classloader hierarchies was added to jcmd. Here is a simple screen snapshot of that support for classloaders hierarchies in action.

As Fränkel concludes in his post, "The JDK offers a lot of out-of-box tools to help developers" and "they are a huge asset in a developer’s day-to-day job." This sentiment applies whether one chooses to use the individual JDK-provided tools or chooses to use jcmd.

Saturday, February 2, 2019

Revealing the Queries Behind psql's Backslash Commands

PostgreSQL's psql interactive terminal tool provides several useful "backslash list commands" such as \d (lists "relations" such as tables, views, indexes, and sequences), \dt (lists tables), \di (lists indexes), \ds (lists sequences), \dv (lists views), \df (lists functions), \du (lists roles), and \? (displays help/usage details on backslash commands). These commands are concise and much simpler to use than writing the queries against PostgreSQL system catalogs (pg_class, pg_roles, pg_namespace, pg_trigger, pg_index, etc.) and information_schema that would provide the same types of details.

Although the psql backslash commands are easier to use than their associated queries, there are situations when it is important to know the full query behind a particular command. These situations include needing to perform a slightly different/adapted query from that associated with the pre-built command and needing to perform similar queries in scripts or code that are being used as PostgreSQL clients instead of psql. These situations make it important to be able to determine what queries psql is performing and the psql option -E (or --echo-hidden) allow that.

The PostgreSQL psql documentation states that the psql options -E and --echo-hidden "echo the actual queries generated by \d and other backslash commands." The documentation adds commentary on why this is useful, "You can use this to study psql's internal operations." When psql is started with the -E or --echo-hidden options, it will display the query associated with a backslash command before executing that command. The next screen snapshot illustrates this for the \du command used to show roles.

From use of psql -E and execution of the command \du, we're able to see that the query underlying \du is this:

Although the query is not nearly as nice to use as \du, we are now able to adapt this query for a related but different use case and are able to run this query from a PostgreSQL client other than psql.

Thursday, January 31, 2019

JDK 9/JEP 280: String Concatenations Will Never Be the Same

JEP 280 ("Indify String Concatenation") was implemented in conjunction with JDK 9 and, according to its "Summary" section, "Change[s] the static String-concatenation bytecode sequence generated by javac to use invokedynamic calls to JDK library functions." The impact this has on string concatenation in Java is most easily seen by looking at the javap output of classes using string concatenation that are compiled in pre-JDK 9 and post-JDK 9 JDKs.

The following simple Java class named "HelloWorldStringConcat" will be used for the first demonstration.

Contrasting of the differences in -verbose output from javap for the HelloWorldStringConcat class's main(String) method when compiled with JDK 8 (AdoptOpenJDK) and JDK 11 (Oracle OpenJDK) is shown next. I have highlighted some key differences.

JDK 8 javap Output

Classfile /C:/java/examples/helloWorld/classes/dustin/examples/HelloWorldStringConcat.class
  Last modified Jan 28, 2019; size 625 bytes
  MD5 checksum 3e270bafc795b47dbc2d42a41c8956af
  Compiled from "HelloWorldStringConcat.java"
public class dustin.examples.HelloWorldStringConcat
  minor version: 0
  major version: 52
     . . .
  public static void main(java.lang.String[]);
    descriptor: ([Ljava/lang/String;)V
    flags: ACC_PUBLIC, ACC_STATIC
    Code:
      stack=4, locals=1, args_size=1
         0: getstatic     #2                  // Field java/lang/System.out:Ljava/io/PrintStream;
         3: new           #3                  // class java/lang/StringBuilder
         6: dup
         7: invokespecial #4                  // Method java/lang/StringBuilder."":()V
        10: ldc           #5                  // String Hello,
        12: invokevirtual #6                  // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
        15: aload_0
        16: iconst_0
        17: aaload
        18: invokevirtual #6                  // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
        21: invokevirtual #7                  // Method java/lang/StringBuilder.toString:()Ljava/lang/String;
        24: invokevirtual #8                  // Method java/io/PrintStream.println:(Ljava/lang/String;)V
        27: return

JDK 11 javap Output

Classfile /C:/java/examples/helloWorld/classes/dustin/examples/HelloWorldStringConcat.class
  Last modified Jan 28, 2019; size 908 bytes
  MD5 checksum 0e20fe09f6967ba96124abca10d3e36d
  Compiled from "HelloWorldStringConcat.java"
public class dustin.examples.HelloWorldStringConcat
  minor version: 0
  major version: 55
     . . .
  public static void main(java.lang.String[]);
    descriptor: ([Ljava/lang/String;)V
    flags: (0x0009) ACC_PUBLIC, ACC_STATIC
    Code:
      stack=3, locals=1, args_size=1
         0: getstatic     #2                  // Field java/lang/System.out:Ljava/io/PrintStream;
         3: aload_0
         4: iconst_0
         5: aaload
         6: invokedynamic #3,  0              // InvokeDynamic #0:makeConcatWithConstants:(Ljava/lang/String;)Ljava/lang/String;
        11: invokevirtual #4                  // Method java/io/PrintStream.println:(Ljava/lang/String;)V
        14: return

The "Description" section of JEP 280 describes this difference: "The idea is to replace the entire StringBuilder append dance with a simple invokedynamic call to java.lang.invoke.StringConcatFactory, that will accept the values in the need of concatenation." This same section shows a similar comparison of compiled output for a similar string concatenation example.

The compiled output from JDK 11 for the simple string concatenation is not just fewer lines than its JDK 8 counterpart; it also has fewer "expensive" operations. Potential performance improvement can be gained from not needing to wrap primitive types, and not needing to instantiate a bunch of extra objects. One of the primary motivations for this change was to "lay the groundwork for building optimized String concatenation handlers, implementable without the need to change the Java-to-bytecode compiler" and to "enable future optimizations of String concatenation without requiring further changes to the bytecode emitted by javac."

JEP 280 Does Not Affect StringBuilder or StringBuffer

There's an interesting implication of this in terms of using StringBuffer (which I have a difficult time finding a good use for anyway) and StringBuilder. It was a stated "Non-Goal" of JEP 280 to not "introduce any new String and/or StringBuilder APIs that might help to build better translation strategies." Related to this, for simple string concatenations like that shown in the code example at the beginning of this post, explicit use of StringBuilder and StringBuffer will actually preclude the ability for the compiler to make use of the JEP 280-introduced feature discussed in this post.

The next two code listings show similar implementations to the simple application shown above, but these use StringBuilder and StringBuffer respectively instead of string concatenation. When javap -verbose is executed against these classes after they are compiled with JDK 8 and with JDK 11, there are no significant differences in the main(String[]) methods.

Explicit StringBuilder Use in JDK 8 and JDK 11 Are The Same

JDK 8 javap Output for HelloWorldStringBuilder.main(String[])

Classfile /C:/java/examples/helloWorld/classes/dustin/examples/HelloWorldStringBuilder.class
  Last modified Jan 28, 2019; size 627 bytes
  MD5 checksum e7acc3bf0ff5220ba5142aed7a34070f
  Compiled from "HelloWorldStringBuilder.java"
public class dustin.examples.HelloWorldStringBuilder
  minor version: 0
  major version: 52
     . . .
  public static void main(java.lang.String[]);
    descriptor: ([Ljava/lang/String;)V
    flags: ACC_PUBLIC, ACC_STATIC
    Code:
      stack=4, locals=1, args_size=1
         0: getstatic     #2                  // Field java/lang/System.out:Ljava/io/PrintStream;
         3: new           #3                  // class java/lang/StringBuilder
         6: dup
         7: invokespecial #4                  // Method java/lang/StringBuilder."":()V
        10: ldc           #5                  // String Hello,
        12: invokevirtual #6                  // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
        15: aload_0
        16: iconst_0
        17: aaload
        18: invokevirtual #6                  // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
        21: invokevirtual #7                  // Method java/lang/StringBuilder.toString:()Ljava/lang/String;
        24: invokevirtual #8                  // Method java/io/PrintStream.println:(Ljava/lang/String;)V
        27: return

JDK 11 javap Output for HelloWorldStringBuilder.main(String[])

Classfile /C:/java/examples/helloWorld/classes/dustin/examples/HelloWorldStringBuilder.class
  Last modified Jan 28, 2019; size 627 bytes
  MD5 checksum d04ee3735ce98eb6237885fac86620b4
  Compiled from "HelloWorldStringBuilder.java"
public class dustin.examples.HelloWorldStringBuilder
  minor version: 0
  major version: 55
     . . .
  public static void main(java.lang.String[]);
    descriptor: ([Ljava/lang/String;)V
    flags: (0x0009) ACC_PUBLIC, ACC_STATIC
    Code:
      stack=4, locals=1, args_size=1
         0: getstatic     #2                  // Field java/lang/System.out:Ljava/io/PrintStream;
         3: new           #3                  // class java/lang/StringBuilder
         6: dup
         7: invokespecial #4                  // Method java/lang/StringBuilder."":()V
        10: ldc           #5                  // String Hello,
        12: invokevirtual #6                  // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
        15: aload_0
        16: iconst_0
        17: aaload
        18: invokevirtual #6                  // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
        21: invokevirtual #7                  // Method java/lang/StringBuilder.toString:()Ljava/lang/String;
        24: invokevirtual #8                  // Method java/io/PrintStream.println:(Ljava/lang/String;)V
        27: return

Explicit StringBuffer Use in JDK 8 and JDK 11 Are The Same

JDK 8 javap Output for HelloWorldStringBuffer.main(String[])

Classfile /C:/java/examples/helloWorld/classes/dustin/examples/HelloWorldStringBuffer.class
  Last modified Jan 28, 2019; size 623 bytes
  MD5 checksum fdfb90497db6a3494289f2866b9a3a8b
  Compiled from "HelloWorldStringBuffer.java"
public class dustin.examples.HelloWorldStringBuffer
  minor version: 0
  major version: 52
     . . .
  public static void main(java.lang.String[]);
    descriptor: ([Ljava/lang/String;)V
    flags: ACC_PUBLIC, ACC_STATIC
    Code:
      stack=4, locals=1, args_size=1
         0: getstatic     #2                  // Field java/lang/System.out:Ljava/io/PrintStream;
         3: new           #3                  // class java/lang/StringBuffer
         6: dup
         7: invokespecial #4                  // Method java/lang/StringBuffer."":()V
        10: ldc           #5                  // String Hello,
        12: invokevirtual #6                  // Method java/lang/StringBuffer.append:(Ljava/lang/String;)Ljava/lang/StringBuffer;
        15: aload_0
        16: iconst_0
        17: aaload
        18: invokevirtual #6                  // Method java/lang/StringBuffer.append:(Ljava/lang/String;)Ljava/lang/StringBuffer;
        21: invokevirtual #7                  // Method java/lang/StringBuffer.toString:()Ljava/lang/String;
        24: invokevirtual #8                  // Method java/io/PrintStream.println:(Ljava/lang/String;)V
        27: return

JDK 11 javap Output for HelloWorldStringBuffer.main(String[])

Classfile /C:/java/examples/helloWorld/classes/dustin/examples/HelloWorldStringBuffer.class
  Last modified Jan 28, 2019; size 623 bytes
  MD5 checksum e4a83b6bb799fd5478a65bc43e9af437
  Compiled from "HelloWorldStringBuffer.java"
public class dustin.examples.HelloWorldStringBuffer
  minor version: 0
  major version: 55
     . . .
  public static void main(java.lang.String[]);
    descriptor: ([Ljava/lang/String;)V
    flags: (0x0009) ACC_PUBLIC, ACC_STATIC
    Code:
      stack=4, locals=1, args_size=1
         0: getstatic     #2                  // Field java/lang/System.out:Ljava/io/PrintStream;
         3: new           #3                  // class java/lang/StringBuffer
         6: dup
         7: invokespecial #4                  // Method java/lang/StringBuffer."":()V
        10: ldc           #5                  // String Hello,
        12: invokevirtual #6                  // Method java/lang/StringBuffer.append:(Ljava/lang/String;)Ljava/lang/StringBuffer;
        15: aload_0
        16: iconst_0
        17: aaload
        18: invokevirtual #6                  // Method java/lang/StringBuffer.append:(Ljava/lang/String;)Ljava/lang/StringBuffer;
        21: invokevirtual #7                  // Method java/lang/StringBuffer.toString:()Ljava/lang/String;
        24: invokevirtual #8                  // Method java/io/PrintStream.println:(Ljava/lang/String;)V
        27: return

JDK 8 and JDK 11 Handling of Looped String Concatenation

For my last example of JEP 280 changes in action, I use a code sample that may offend some Java developers' sensibilities and perform a string concatenation within a loop. Keep in mind this is just an illustrative example and all will be okay, but don't try this at home.

JDK 8 javap Output for HelloWorldStringConcatComplex.main(String[])

Classfile /C:/java/examples/helloWorld/classes/dustin/examples/HelloWorldStringConcatComplex.class
  Last modified Jan 30, 2019; size 766 bytes
  MD5 checksum 772c4a283c812d49451b5b756aef55f1
  Compiled from "HelloWorldStringConcatComplex.java"
public class dustin.examples.HelloWorldStringConcatComplex
  minor version: 0
  major version: 52
     . . .
  public static void main(java.lang.String[]);
    descriptor: ([Ljava/lang/String;)V
    flags: ACC_PUBLIC, ACC_STATIC
    Code:
      stack=2, locals=3, args_size=1
         0: ldc           #2                  // String Hello
         2: astore_1
         3: iconst_0
         4: istore_2
         5: iload_2
         6: bipush        25
         8: if_icmpge     36
        11: new           #3                  // class java/lang/StringBuilder
        14: dup
        15: invokespecial #4                  // Method java/lang/StringBuilder."":()V
        18: aload_1
        19: invokevirtual #5                  // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
        22: iload_2
        23: invokevirtual #6                  // Method java/lang/StringBuilder.append:(I)Ljava/lang/StringBuilder;
        26: invokevirtual #7                  // Method java/lang/StringBuilder.toString:()Ljava/lang/String;
        29: astore_1
        30: iinc          2, 1
        33: goto          5
        36: getstatic     #8                  // Field java/lang/System.out:Ljava/io/PrintStream;
        39: aload_1
        40: invokevirtual #9                  // Method java/io/PrintStream.println:(Ljava/lang/String;)V
        43: return

JDK 11 javap Output for HelloWorldStringConcatComplex.main(String[])

Classfile /C:/java/examples/helloWorld/classes/dustin/examples/HelloWorldStringConcatComplex.class
  Last modified Jan 30, 2019; size 1018 bytes
  MD5 checksum 967fef3e7625965ef060a831edb2a874
  Compiled from "HelloWorldStringConcatComplex.java"
public class dustin.examples.HelloWorldStringConcatComplex
  minor version: 0
  major version: 55
     . . .
  public static void main(java.lang.String[]);
    descriptor: ([Ljava/lang/String;)V
    flags: (0x0009) ACC_PUBLIC, ACC_STATIC
    Code:
      stack=2, locals=3, args_size=1
         0: ldc           #2                  // String Hello
         2: astore_1
         3: iconst_0
         4: istore_2
         5: iload_2
         6: bipush        25
         8: if_icmpge     25
        11: aload_1
        12: iload_2
        13: invokedynamic #3,  0              // InvokeDynamic #0:makeConcatWithConstants:(Ljava/lang/String;I)Ljava/lang/String;
        18: astore_1
        19: iinc          2, 1
        22: goto          5
        25: getstatic     #4                  // Field java/lang/System.out:Ljava/io/PrintStream;
        28: aload_1
        29: invokevirtual #5                  // Method java/io/PrintStream.println:(Ljava/lang/String;)V
        32: return

In the presentation "Enough java.lang.String to Hang Ourselves ...," Dr. Heinz M. Kabutz and Dmitry Vyazelenko discuss the JEP 280-introduced changes to Java string concatenation and summarize it succinctly, "+ is no longer compiled to StringBuilder." In their "Lessons from Today" slide, they state, "Use + instead of StringBuilder where possible" and "recompile classes for Java 9+."

The changes implemented in JDK 9 for JEP 280 "will enable future optimizations of String concatenation without requiring further changes to the bytecode emitted by javac." Interestingly, it was recently announced that JEP 348 ("Java Compiler Intrinsics for JDK APIs") is now a Candidate JEP and it aims to use a similar approach for compiling methods String::format and Objects::hash.