Thursday, December 31, 2020

Java Predictions for 2021 on Foojay

Geertjan Wielenga has posted "Java Predictions for 2021" on Foojay Today. It is a collection of predictions about Java in 2021 from eight members of the Java community (Almas Baimagambetov, Stephen Chin, Brice Dutheil, Marcus Hirt, Reza Rahman, Matt Raible, Simon Ritter, and me). The predictions are concisely written and it's interesting to see the overlap between them while at the same time seeing how different parts of "Java" are important to different people. In this post, I elaborate a bit more on my predictions that were included in "Java Predictions for 2021".

I provided two somewhat related predictions for Java in 2021:

  • "Records will likely be finalized in 2021 and will be widely popular with Java developers who are fortunate enough to work on a version of the JDK with final (not preview) Record support.
  • The release of the OpenJDK 17 implementation in 2021 (which will be the foundation of Oracle's LTS version and other community members' LTS versions) will motivate many who are already working on versions of the JDK later than JDK 8 to start moving or investigate moving to JDK 17. However, JDK 8 will remain widely popular (probably will still be used by over half of Java developers), creating (in the long term) a bimodal distribution of most commonly used JDK versions (8 and 17)."

Java Records Final in 2021

The prediction that Java Records will be final in 2021 is not a risky one. Records have a been a "preview" feature in JDK 14 (JEP 359) and JDK 15 (JEP 384) and now JEP 395 "proposes to finalize the feature in JDK 16" (which is currently in Rampdown Phase One and is scheduled to be released for General Availability in March 2021). Because Records have been through two preview releases already, it seems unlikely that they won't be final as of JDK 16. In the event that they do need one more release, JDK 17 should be released in October 2021.

And Then There Were Two: JDK 8 and JDK 17

2021 will see the beginning of a move to a bimodial distribution of JDK releases most commnoly used. With JDK 17's likely release in October 2021, we'll likely see many Java shops that have already migrated to a version of JDK later than JDK 8 move to that newly released JDK 17. There have been some nice additions and improvements to OpenJDK (which is the foundation of several different JDK implementations) that have been added in recent versions of the JDK and JDK 17 will be an "LTS" (Long-term Support) release for many of the JDK implementations. As an "LTS" release, JDK 17 will appeal to Java shops that want to only be on versions with long-term support and JDK 17 will be the first since JDK 11 to have this status for many of the JDK implementations.

JDK 8 appears to still be the most widely used release of the Java even in 2020. There are several metrics and andecdotal evidence that suggest this. One example is the JetBrains 2020 Development Ecosystem survey that suggests that 75% of Java developers responding to the survey use JDK 8 (some of these developers use other versions of JDK as well) and the same chart shows 32% of responding Java developers use JDK 11. For reference, the 2019 and 2018 versions of this same survey indicated that 83% and 84% of Java developers used JDK 8 in 2019 and 2018 respectively.

JDK 8 is a version with long-term support (Oracle, for example, offers "extended support" for JDK 8 through December 2030) in several JDK implementations and some shops appear hesitant to move to JDK 9 with its introduced modularity support (and need for libraries and frameworks to support that as well). For those shops that have already migrated to a version of JDK later than JDK 8, it should be relatively easier to migrate to JDK 17. I think that some JDK 8 shops will be motivated to make the "big move" and, while doing that, will jump directly to JDK 17. However, I expect that we'll still see at least half of JDK developers still using JDK 8 even at the end of 2021. For the half of JDK users already using a version later than JDK 8 (not counting users of version of JDK before JDK 8), I think we'll begin to see them migrate to JDK 17 in 2021 and the following year or two. Within the next year or two, I expect most JDK developers will be working with JDK 8 or JDK 17.

There will, of course, be some small pockets of JDK developers using other versions before JDK 8, between JDK 8 and JDK 17 (perhaps because they use a feature or garbage collector no longer available in JDK 17), and newer versions of JDK as they are released in 2022.

"LTS" Among JDK Providers

The following are some roadmaps of various JDK vendors' JDK implementations that provide insight into each vendor's LTS concept. Although "LTS" often is referring to Oracle's plan regarding their JDK implementation built on top of OpenJDK, other JDK vendors have generally treated these "LTS" releases in similar manner.

  • AdoptOpenJDK Support and Release Roadmap
    • Shows "Java 17" as LTS.
    • States, "In addition, every three years one feature release will be designated as a Long Term Supported (LTS) release. We will produce LTS releases for at least four years."
    • States, "As a general philosophy, AdoptOpenJDK will continue to build binaries for LTS releases as long as the corresponding upstream source is actively maintained."
  • Oracle Java SE Support Roadmap
    • States, "For product releases after Java SE 8, Oracle will designate a release, every three years, as a Long-Term-Support (LTS) release. Java SE 11 is an LTS release."
  • Azul Java Support Roadmap
    • References Long Term Support (LTS) and Medium Term Support (MTS) and states, "Releases designated as LTS are those same LTS releases as designated by Oracle and the OpenJDK community."
  • Amazon Corretto
    • "Amazon Corretto 8 & 11 support extended" states, "Amazon is extending long-term support (LTS) for Amazon Corretto 8 from June 2023 to May 2026 and for Amazon Corretto 11 from August 2024 to September 2027. Long-term support (LTS) for Corretto includes security updates and specific performance enhancements released at least quarterly."

Looking Forward to 2021

Most of us are hoping for a better year in 2021 than we've experienced in 2020. The finalization of Java Records and General Availability of JDK 17 in 2021 are going to be significant positive events for Java developers and I'm hoping that these will only be a small representative sample of positive events and advancements that benefit a much wider population in 2021.

Monday, December 7, 2020

JDK 16: Stream to List In One Easy Call

As Java functional streams have become increasingly popular, an increasing number of requests is being made for new stream operations to be supported. Amidst these requests for numerous disparate new operations, one operation that seems to be requested more than the others is an operation that directly provides a List from a Stream. JDK 16 Early Access Build 27 introduces Stream.toList(), which is the subject of this post.

Before the JDK 16 Early Access Build 27 introduction of Stream.toList(), the most common approach for acquiring a List from a Stream was to invoke the approprite Collector:

stream.collect(Collectors.toList())

This is not a lot of code and it's fairly straightforward once you see it, but many have wanted an even more concise syntax for this frequently used stream operation. JDK 16 brings us this:

stream.toList()

It may be tempting to go into one's code base and use stream.toList() as a drop-in replacement for stream.collect(Collectors.toList()), but there may be differences in behavior if the code has a direct or indirect dependency on the implementation of stream.collect(Collectors.toList()) returning an ArrayList. Some of the key differences between the List returned by stream.collect(Collectors.toList()) and stream.toList() are spelled out in the remainder of this post.

The Javadoc-based documentation for Collectors.toList() states (emphasis added), "Returns a Collector that accumulates the input elements into a new List. There are no guarantees on the type, mutability, serializability, or thread-safety of the List returned..." Although there are no guarantees regarding the "type, mutability, serializability, or thread-safety" on the List provided by Collectors.toList(), it is expected that some may have realized it's currently an ArrayList and have used it in ways that depend on the characteristics of an ArrayList.

The following code snippet (full code listing on GitHub) shows a method that can be executed against the List implementations returned by Collectors.toList() and Stream.toList() to see what they have in common and how the are different.

/**
 * Analyzes the supplied {@code List} and writes to standard output
 * some key characteristics of the supplied {@code List}.
 *
 * @param listDescription Description of {@code List} to be analyzed.
 * @param listUnderAnalysis {@code List} to be analyzed.
 */
private static void analyzeList(
   final String listDescription, final List<String> listUnderAnalysis)
{
   out.println(listDescription + ": ");
   out.println("\tClass Type: " + listUnderAnalysis.getClass().getCanonicalName());
   out.println("\tAble to add to List? " + isListAddCapable(listUnderAnalysis));
   out.println("\tAble to sort List?   " + isListSortable(listUnderAnalysis));
}

When the simple analysis code above is executed against implementations of List returned by Stream.collect(Collectors.toList()) and Stream.toList(), the output appears as shown next.

Stream.collect(Collectors.toList()): 
	Class Type: java.util.ArrayList
	Able to add to List? true
	Able to sort List?   true
Stream.toList(): 
	Class Type: java.util.ImmutableCollections.ListN
	Able to add to List? false
	Able to sort List?   false
[NOT Stream] List.of(): 
	Class Type: java.util.ImmutableCollections.ListN
	Able to add to List? false
	Able to sort List?   false

The output shown above demonstrates that Stream.toList() provides a List implementation that is immutable (type ImmutableCollections.ListN that cannot be added to or sorted) similar to that provided by List.of() and in contrast to the mutable (can be changed and sorted) ArrayList provided by Stream.collect(Collectors.toList()). Any existing code depending on the ability to mutate the ArrayList returned by Stream.collect(Collectors.toList()) will not work with Stream.toList() and an UnsupportedOperationException will be thrown.

Although the implementation nature of the Lists returned by Stream.collect(Collectors.toList()) and Stream.toList() are very different, they still both implement the List interface and so they are considered equal when compared using List.equals(Object). This is demonstrated in the full code listing on GitHub.

The addition of method toList() to the Stream interface is a small thing, but it does make an often-used technique more convenient.

Saturday, December 5, 2020

Java's String.repeat Method in Action: Building PreparedStatement with Dynamic Number of Parameters

Java's String.repeat(int) method is an example of a "small" addition to Java (introduced with JDK 11) that I find myself frequently using and appreciating. This post describes use of JDK 11-introduced String.repeat(int) for easier custom generation of SQL WHERE clauses with the appropriate number of "?" parameter placeholders for use with PreparedStatements.

Many Java developers do not need to manually build PreparedStatements with the approprite number of parameter placeholders because they take advantage of a JPA implementation, other ORM framework, or library that handles it for them. However, the demonstrations in this post show how String.repeat(int) can make light work of any implementation that needs to build up a string with a specified number of repeated pieces.

 

Building SQL IN Condition with Dynamic Number of Parameters

A common approach used in Java applications for building a custom SQL SELECT statement that queries a particular database column against a collection of potential values is to use the IN operator and pass all potential matching values to that IN operator.

One Java implementation approach for building the IN operator portion of the SQL SELECT's WHERE clause is to iterate the same number of times as there are parameters for the IN operator and to use a conditional within that loop to determine the how to properly add that portion of the in-progress IN portion. This is demonstrated in the next code listing:

/**
 * Demonstrates "traditional" approach for building up the
 * "IN" portion of a SQL statement with multiple parameters
 * that uses a conditional within a loop on the number of
 * parameters to determine how to best handle each.
 *
 * @param columnName Name of database column to be referenced
 *    in the "IN" clause.
 * @param numberPlaceholders Number of parameters for which
 *    placeholder question marks ("?") need to be added.
 * @return The "IN" portion of a SQL statement with the
 *    appropriate number of placeholder question marks.
 */
public String generateInClauseTraditionallyOne(
   final String columnName, final int numberPlaceholders)
{
   final StringBuilder inClause = new StringBuilder();
   inClause.append(columnName + " IN (");
   for (int placeholderIndex = 0; placeholderIndex < numberPlaceholders; placeholderIndex++)
   {
      if (placeholderIndex != numberPlaceholders-1)
      {
         inClause.append("?, ");
      }
      else
      {
         inClause.append("?");
      }
   }
   inClause.append(")");
   return inClause.toString();
}

A second traditional approach for building up the IN clause to use a dynamic number of parameter placeholders is to again loop the same number of times as there are parameters, but append exactly the same new text each iteration. After iteration is completed, the extra characters are chopped off the end. This approach is shown in the next code listing:

/**
 * Demonstrates "traditional" approach for building up the
 * "IN" portion of a SQL statement with multiple parameters
 * that treats each looped-over parameter index the same and
 * the removes the extraneous syntax from the end of the
 * generated string.
 *
 * @param columnName Name of database column to be referenced
 *    in the "IN" clause.
 * @param numberPlaceholders Number of parameters for which
 *    placeholder question marks ("?") need to be added.
 * @return The "IN" portion of a SQL statement with the
 *    appropriate number of placeholder question marks.
 */
public String generateInClauseTraditionallyTwo(
   final String columnName, final int numberPlaceholders)
{
   final StringBuilder inClause = new StringBuilder();
   inClause.append(columnName + " IN (");
   for (int placeholderIndex = 0; placeholderIndex < numberPlaceholders; placeholderIndex++)
   {
      inClause.append("?, ");
   }
   inClause.delete(inClause.length()-2, inClause.length());
   inClause.append(")");
   return inClause.toString();
}

JDK 11 introduced a set of useful new String methods that include String.repeat(int). The String.repeat(int) method boils these approaches for generating a custom IN operator with dynamic number of parameter placeholders to a single line as shown in the next code listing:

/**
 * Demonstrates JDK 11 {@link String#repeat(int)} approach
 * for building up the "IN" portion of a SQL statement with
 * multiple parameters.
 *
 * @param columnName Name of database column to be referenced
 *    in the "IN" clause.
 * @param numberPlaceholders Number of parameters for which
 *    placeholder question marks ("?") need to be added.
 * @return The "IN" portion of a SQL statement with the
 *    appropriate number of placeholder question marks.
 */
public String generateInClauseWithStringRepeat(
   final String columnName, final int numberPlaceholders)
{
   return columnName + " IN (" + "?, ".repeat(numberPlaceholders-1) + "?)";
}

With the use of String.repeat(int), a single line accomplishes the task at hand and there's no need for explicit looping or explicit instantiation of a StringBuilder.

 

Building SQL OR Conditions with Dynamic Number of Parameters

Multiple SQL or conditions can be used instead of IN to test against multiple values. This is a must if, for example, the number of paramaters is over 1000 and you're using an Oracle database that only allows IN to support up to 1000 elements.

As with use of the IN condition, two commonly used approaches for building up the OR conditions for a dynamic number of parameter placeholders are to either to loop with a condition checking that each entry's output is written correctly as it's written or to remove extraneous characters after looping. These two approaches are shown in the next code listing:

/**
 * Demonstrates "traditional" approach for building up the
 * "OR" portions of a SQL statement with multiple parameters
 * that uses a conditional within a loop on the number of
 * parameters to determine how to best handle each.
 *
 * @param columnName Name of database column to be referenced
 *    in the "OR" clauses.
 * @param numberPlaceholders Number of parameters for which
 *    placeholder question marks ("?") need to be added.
 * @return The "OR" portions of a SQL statement with the
 *    appropriate number of placeholder question marks.
 */
public String generateOrClausesTraditionallyOne(
   final String columnName, final int numberPlaceholders)
{
   final StringBuilder orClauses = new StringBuilder();
   for (int placeholderIndex = 0; placeholderIndex < numberPlaceholders; placeholderIndex++)
   {
      if (placeholderIndex != numberPlaceholders-1)
      {
         orClauses.append(columnName).append(" = ? OR ");
      }
      else
      {
         orClauses.append(columnName).append(" = ?");
      }
   }
   return orClauses.toString();
}

/**
 * Demonstrates "traditional" approach for building up the
 * "OR" portions of a SQL statement with multiple parameters
 * that treats each looped-over parameter index the same and
 * the removes the extraneous syntax from the end of the
 * generated string.
 *
 * @param columnName Name of database column to be referenced
 *    in the "OR" clauses.
 * @param numberPlaceholders Number of parameters for which
 *    placeholder question marks ("?") need to be added.
 * @return The "OR" portions of a SQL statement with the
 *    appropriate number of placeholder question marks.
 */
public String generateOrClausesTraditionallyTwo(
   final String columnName, final int numberPlaceholders)
{
   final StringBuilder orClauses = new StringBuilder();
   for (int placeholderIndex = 0; placeholderIndex < numberPlaceholders; placeholderIndex++)
   {
      orClauses.append(columnName + " = ? OR ");
   }
   orClauses.delete(orClauses.length()-4, orClauses.length());
   return orClauses.toString();
}

The use of String.repeat(int) makes this easy as well:

/**
 * Demonstrates JDK 11 {@link String#repeat(int)} approach
 * for building up the "OR" portions of a SQL statement with
 * multiple parameters.
 *
 * @param columnName Name of database column to be referenced
 *    in the "OR" clauses.
 * @param numberPlaceholders Number of parameters for which
 *    placeholder question marks ("?") need to be added.
 * @return The "OR" portions of a SQL statement with the
 *    appropriate number of placeholder question marks.
 */
public String generateOrClausesWithStringRepeat(
   final String columnName, final int numberPlaceholders)
{
   final String orPiece = columnName + " = ? OR ";
   return orPiece.repeat(numberPlaceholders-1) + columnName + " = ?";
}

 

Conclusion

The introduction of String.repeat(int) makes it easer for Java developers to implement custom generation of Java Strings that consist of dynamically repeated portions.

All code snippets shown in this post are available on GitHub.

Saturday, November 28, 2020

JDK 16: Checking Indexes and Ranges of Longs

In my last post, I described the day period support added with JDK 16 Early Access Build 25. That same build also added methods for checking indexes and ranges of long values, which is the subject of this post.

JDK-8255150 ("Add utility methods to check long indexes and ranges") is the Enhancement used to add utility methods for checking long indexes and ranges similar to what JDK-8135248 ("Add utility methods to check indexes and ranges") added for integers with JDK 9. JDK-8255150 states, "The goal is to add a similar set of methods [as JDK-8135248] but rather than operate on int arguments, the new methods operate on long arguments."

JDK-8255150 lists the method signatures for the three new methods being added to the Objects class (descriptions provided by JDK-8135248):

  • Checking whether an index is within bounds:
    public static long checkIndex(long index, long length)
  • Checking whether an absolute range is within bounds:
    public static long checkFromToIndex(long fromIndex, long toIndex, long length)
  • Checking whether a relative range is within bounds:
    public static long checkFromIndexSize(long fromIndex, long size, long length)

Because these new methods "mirror the int utility methods," it is useful to look at JDK-8135248 to see more historical context for the justification for the introduction of these methods. That Enhancement states, "There are numerous methods in the JDK that check if an index or an absolute/relative range is valid before accessing the contents of an array (or in general a memory region for the case of a direct java.nio.ByteBuffer). ... Such checks, while not difficult, are often easy to get wrong and optimize correctly, thus there is a risk to the integrity and security of the runtime."

JDK-8135248 also talks about possibilities for optimization, "A further desire for such methods is some or all can be made intrinsic (see JDK-8042997), thus hinting to the HotSpot runtime compiler to use unsigned comparisons and better optimize array access (via aaload/store or Unsafe) in loops (especially those that are unrolled)."

A class that demonstrates these newly added methods, LongIndexRangeChecksDemo, is available on GitHub. All examples in this class demonstrate the various checks throwing IndexOutOfBoundsExceptions to indicate that the proposed index and/or size values do not fall within the allowed range. The main(String[]) function executes all the example methods and its output is divided into described sections below.

checkIndex Example Output

The message associated with this example clearly describes the index that is out of bounds and how that index is out of bounds.

==========================
== checkIndex Exception ==
==========================
java.lang.IndexOutOfBoundsException: Index 7 out of bounds for length 5
	at java.base/jdk.internal.util.Preconditions.outOfBounds(Preconditions.java:64)
	at java.base/jdk.internal.util.Preconditions.outOfBoundsCheckIndex(Preconditions.java:88)
	at java.base/jdk.internal.util.Preconditions.checkIndex(Preconditions.java:412)
	at java.base/java.util.Objects.checkIndex(Objects.java:435)
	at dustin.examples.jdk16.check.LongIndexRangeChecksDemo.lambda$demoCheckIndexException$0(LongIndexRangeChecksDemo.java:34)
	at dustin.examples.jdk16.check.LongIndexRangeChecksDemo.executeDemonstration(LongIndexRangeChecksDemo.java:96)
	at dustin.examples.jdk16.check.LongIndexRangeChecksDemo.demoCheckIndexException(LongIndexRangeChecksDemo.java:33)
	at dustin.examples.jdk16.check.LongIndexRangeChecksDemo.main(LongIndexRangeChecksDemo.java:115)

checkFromToIndex Example Output

The message clearly indicates that the range specified by the "from" and "to" indexes is too large for the expected length capacity. Note that the "[" opening the range description indicates "inclusive" and the ")" ending the range description indicates "exclusive".

================================
== checkFromToIndex Exception ==
================================
java.lang.IndexOutOfBoundsException: Range [2, 6) out of bounds for length 3
	at java.base/jdk.internal.util.Preconditions.outOfBounds(Preconditions.java:64)
	at java.base/jdk.internal.util.Preconditions.outOfBoundsCheckFromToIndex(Preconditions.java:94)
	at java.base/jdk.internal.util.Preconditions.checkFromToIndex(Preconditions.java:459)
	at java.base/java.util.Objects.checkFromToIndex(Objects.java:461)
	at dustin.examples.jdk16.check.LongIndexRangeChecksDemo.lambda$demoCheckFromToIndexException$1(LongIndexRangeChecksDemo.java:48)
	at dustin.examples.jdk16.check.LongIndexRangeChecksDemo.executeDemonstration(LongIndexRangeChecksDemo.java:96)
	at dustin.examples.jdk16.check.LongIndexRangeChecksDemo.demoCheckFromToIndexException(LongIndexRangeChecksDemo.java:47)
	at dustin.examples.jdk16.check.LongIndexRangeChecksDemo.main(LongIndexRangeChecksDemo.java:116)

checkFromIndexSize Example Output

This example indicates that the range formed by a "from" index and range size is out of bounds for the specified length capacity.

==================================
== checkFromIndexSize Exception ==
==================================
java.lang.IndexOutOfBoundsException: Range [2, 2 + 6) out of bounds for length 3
	at java.base/jdk.internal.util.Preconditions.outOfBounds(Preconditions.java:64)
	at java.base/jdk.internal.util.Preconditions.outOfBoundsCheckFromIndexSize(Preconditions.java:100)
	at java.base/jdk.internal.util.Preconditions.checkFromIndexSize(Preconditions.java:507)
	at java.base/java.util.Objects.checkFromIndexSize(Objects.java:487)
	at dustin.examples.jdk16.check.LongIndexRangeChecksDemo.lambda$demoCheckFromIndexSizeException$2(LongIndexRangeChecksDemo.java:62)
	at dustin.examples.jdk16.check.LongIndexRangeChecksDemo.executeDemonstration(LongIndexRangeChecksDemo.java:96)
	at dustin.examples.jdk16.check.LongIndexRangeChecksDemo.demoCheckFromIndexSizeException(LongIndexRangeChecksDemo.java:61)
	at dustin.examples.jdk16.check.LongIndexRangeChecksDemo.main(LongIndexRangeChecksDemo.java:117)

checkFromIndexSize Overflow Example Output

This example indicates that the range formed by a "from" index and range size are out of bounds because a numeric overflow occurred when adding the size to the initial index. This is a nice catch because an overly simplistic homegrown approach that checked that the supplied initial index and supplied size are both positive and then checked the sum of the index and size against the allowed length would be faulty logic due to the overflow possibility.

=============================================
== checkFromIndexSize (Overflow) Exception ==
=============================================
java.lang.IndexOutOfBoundsException: Range [2, 2 + 9223372036854775807) out of bounds for length 3
	at java.base/jdk.internal.util.Preconditions.outOfBounds(Preconditions.java:64)
	at java.base/jdk.internal.util.Preconditions.outOfBoundsCheckFromIndexSize(Preconditions.java:100)
	at java.base/jdk.internal.util.Preconditions.checkFromIndexSize(Preconditions.java:507)
	at java.base/java.util.Objects.checkFromIndexSize(Objects.java:487)
	at dustin.examples.jdk16.check.LongIndexRangeChecksDemo.lambda$demoCheckFromIndexSizeExceptionOnOverflow$3(LongIndexRangeChecksDemo.java:77)
	at dustin.examples.jdk16.check.LongIndexRangeChecksDemo.executeDemonstration(LongIndexRangeChecksDemo.java:96)
	at dustin.examples.jdk16.check.LongIndexRangeChecksDemo.demoCheckFromIndexSizeExceptionOnOverflow(LongIndexRangeChecksDemo.java:76)
	at dustin.examples.jdk16.check.LongIndexRangeChecksDemo.main(LongIndexRangeChecksDemo.java:118)

Common Uses

The greatest beneficiary of these newly added long-supporting methods may be the authors, maintainers, and users of the foreign memory access API as described in this mailing list message: "We have to jump through quite a few hoops in the implementation of the foreign memory access API in order to leverage the intrinsification of int-based index checks, and even then we are not covering the cases where the numbers are larger than ints. Looking forward to being able to remove those hacks!"

A common use of these methods is likely to be as method guards for checking method parameters against expected preconditions similar to how other Objects' methods such as checkIndex(int, int), checkFromToIndex(int, int, int), checkFromIndexSize(int, int, int), requireNonNull(T), and requireNonNull(T, String) are used.

Monday, November 23, 2020

Day Period Support in JDK 16

JDK 16 Early Access Build 25 (2020/11/18) includes changes for JDK-8247781 ("Day periods support"). As stated in the JDK 16 Early Access Build 25 Release Notes ("Day period support added to java.time formats"), the new functionality "translates day periods defined in Unicode Consortium's CLDR."

In most English-language situations using a "12-hour clock", the "day periods" might be used instead of the AM (ante meridiem) and PM (post meridiem) designators. The Unicode documentation "Day Period Rule Sets" describes day period rules like this: "Each locale can have a set of day period rules, which determine the periods during a day for use in time formats like '10:00 at night', or to select statements like 'Your email arrived last night.' If locales do not have dayPeriodRules, the computation of dayPeriods falls back to AM/PM."

As with most things, it's perhaps easiest to see this new functionality via code examples and their associated output. The first example shown here is adapted from the JDK 16 Early Access Build 25 Release Notes. Note that the "B" is used in the pattern to specify that a day period is to be used.

/**
 * Writes the current day period out to standard output.
 *
 * This is based on the example included with the Release Notes
 * (https://jdk.java.net/16/release-notes).
 */
public void printCurrentDayPeriod()
{
   final String currentDayPeriodStr
      = DateTimeFormatter.ofPattern("B").format(LocalTime.now());
   out.println("Pattern 'B' (time now): \"" + currentDayPeriodStr + "\"");
}

The output for the above code sample when run in evening hours is now shown:

Pattern 'B' (time now): "in the evening"

The next code sample contains two methods, showing variations of date/time formats using the "B" format pattern letter.

/**
 * Prints representation of supplied {@link ZonedDateTime} with hour,
 * day period, and time zone details to standard output.
 *
 * @param zonedDateTime Date/time that will be written to standard output
 *    and will include hour, day period, and zone details.
 */
public void printHourDayPeriodAndZone(final ZonedDateTime zonedDateTime)
{
   final String dateTimeStr
      = DateTimeFormatter.ofPattern("hh B, zzzz").format(zonedDateTime);
   out.println("Hour/Day Period/Zone: \"" + dateTimeStr + "\"");
}

/**
 * Prints representation of supplied {@link ZonedDateTime} with hour,
 * minutes, day period, and time zone details to standard output.
 *
 * @param zonedDateTime Date/time that will be written to standard output
 *    and will include hour, minutes, day period, and zone details.
 */
public void printHourDayMinutePeriodAndZone(final ZonedDateTime zonedDateTime)
{
   final String dateTimeStr
      = DateTimeFormatter.ofPattern("K:mm B z").format(zonedDateTime);
   out.println("Hour/Minute/Day Period/Zone: \"" + dateTimeStr + "\"");
}

The following two lines of output are shown when the two methods above are executed during evening hours:

Hour/Day Period/Zone: "08 in the evening, Mountain Standard Time"
Hour/Minute/Day Period/Zone: "8:07 in the evening MST"

Because the output for the examples to this point were all executed in evening hours, the day period ("in the evening") has been the same for all examples executed above.

The next code listing iterates over each hour of the day to indicate different day period expressions for the different hours when Locale.US is specified. Note that the dates/times constructed in this example have non-zero fractional hours (non-zero minutes, seconds, and nanoseconds).

/**
 * Prints Day Period phraseology for each of 24 hours of day with
 * arbitrary minutes, seconds, and nanoseconds to standard output.
 */
public void printDayPeriodsByHour()
{
   out.println("===== Hours With Non-Zero Minutes/Seconds/Nanoseconds =====");
   final DateTimeFormatter dateTimeFormat = DateTimeFormatter.ofPattern("hh B");
   for (int hour = 0; hour < 24; hour++)
   {
      final OffsetDateTime dateTime
         = Instant.now().atOffset(ZoneOffset.UTC).withHour(hour);
      out.println("Hour " + hour + ": \"" + dateTimeFormat.format(dateTime) + "\"");
   }
}

The output from running the code above shows how different hours correspond to different "day periods" for US.Locale. Note that this output is based on a date/time with non-zero fractional hours (times are not exact hours).

===== Hours With Non-Zero Minutes/Seconds/Nanoseconds =====
Hour 0: "12 at night"
Hour 1: "01 at night"
Hour 2: "02 at night"
Hour 3: "03 at night"
Hour 4: "04 at night"
Hour 5: "05 at night"
Hour 6: "06 in the morning"
Hour 7: "07 in the morning"
Hour 8: "08 in the morning"
Hour 9: "09 in the morning"
Hour 10: "10 in the morning"
Hour 11: "11 in the morning"
Hour 12: "12 in the afternoon"
Hour 13: "01 in the afternoon"
Hour 14: "02 in the afternoon"
Hour 15: "03 in the afternoon"
Hour 16: "04 in the afternoon"
Hour 17: "05 in the afternoon"
Hour 18: "06 in the evening"
Hour 19: "07 in the evening"
Hour 20: "08 in the evening"
Hour 21: "09 at night"
Hour 22: "10 at night"
Hour 23: "11 at night"

Because the date/time used in the example above has non-zero fractional hours, the en_US day period expressions are "at night", "in the morning", "in the afternoon", and "in the evening".

The next code snippet shows a method that ensures the formatted date/time has "exact hours" (its minutes, seconds, and nanoseconds are all zero).

/**
 * Prints Day Period phraseology for each of 24 hours of day with
 * zero minutes, zero seconds, and zero nanoseconds to standard output.
 */
public void printDayPeriodsByWholeHours()
{
   out.println("===== Exact Hours =====");
   final DateTimeFormatter dateTimeFormat = DateTimeFormatter.ofPattern("hh B");
   for (int hour = 0; hour < 24; hour++)
   {
      final OffsetDateTime dateTime = OffsetDateTime.of(
         2020, 11, 23, hour, 0, 0, 0, ZoneOffset.UTC);
      out.println("Hour " + hour + ": \"" + dateTimeFormat.format(dateTime) + "\"");
   }
}

When the above code is executed, the following output is seen:

===== Exact Hours =====
Hour 0: "12 midnight"
Hour 1: "01 at night"
Hour 2: "02 at night"
Hour 3: "03 at night"
Hour 4: "04 at night"
Hour 5: "05 at night"
Hour 6: "06 in the morning"
Hour 7: "07 in the morning"
Hour 8: "08 in the morning"
Hour 9: "09 in the morning"
Hour 10: "10 in the morning"
Hour 11: "11 in the morning"
Hour 12: "12 noon"
Hour 13: "01 in the afternoon"
Hour 14: "02 in the afternoon"
Hour 15: "03 in the afternoon"
Hour 16: "04 in the afternoon"
Hour 17: "05 in the afternoon"
Hour 18: "06 in the evening"
Hour 19: "07 in the evening"
Hour 20: "08 in the evening"
Hour 21: "09 at night"
Hour 22: "10 at night"
Hour 23: "11 at night"

The output above is mostly the same as for the output associated with dates/times that had fractional hours, but the whole hours example is different for hour 0 (day period of "midnight") and hour 12 (day period of "noon").

For my last example, I'm going to use Dominican Republic/Spanish ("es DO") for the Locale with the same code just demonstrated. Here is that output:

Hour 0: "12 de la madrugada"
Hour 1: "01 de la madrugada"
Hour 2: "02 de la madrugada"
Hour 3: "03 de la madrugada"
Hour 4: "04 de la madrugada"
Hour 5: "05 de la madrugada"
Hour 6: "06 de la mañana"
Hour 7: "07 de la mañana"
Hour 8: "08 de la mañana"
Hour 9: "09 de la mañana"
Hour 10: "10 de la mañana"
Hour 11: "11 de la mañana"
Hour 12: "12 del mediodía"
Hour 13: "01 de la tarde"
Hour 14: "02 de la tarde"
Hour 15: "03 de la tarde"
Hour 16: "04 de la tarde"
Hour 17: "05 de la tarde"
Hour 18: "06 de la tarde"
Hour 19: "07 de la tarde"
Hour 20: "08 de la noche"
Hour 21: "09 de la noche"
Hour 22: "10 de la noche"
Hour 23: "11 de la noche"

Support for "day period" presentation in formatted dates/times provides Java developers with more flexibility in expressing day period details than simply using AM and PM. All code listings shown in this post are available on GitHub.

Monday, September 28, 2020

Moving Toward Inline Classes: JEP 390 and the @ValueBased Annotation

Candidate JEP 390 ("Warnings for Value-Based Classes") was announced this past week by Mark Reinhold on the OpenJDK jdk-dev mailing list. This is a JDK Enhancement Proposal that may be more exciting in what it indicates (progress toward inline classes) than what it actually is (annotation and warnings about "improper attempts to synchronize on instances" of @ValueBased-annotated JDK classes that might be come inline classes).

The term "Value-based Classes" was included in the Java SE 8 Javadoc documentation, which identified the following characteristics of "instances of a value-based class" at that time:

  • "final and immutable (though may contain references to mutable objects)"
  • Implement "equals, hashCode, and toString ... solely [computed] from the instance's state and not from its identity or the state of any other object or variable"
  • Do NOT use "identity-sensitive operations such as reference equality (==) between instances, identity hash code of instances, or synchronization on an instances's intrinsic lock"
  • "Are considered equal solely based on equals()" rather than "on reference equality (==)"
  • "Instantiated through factory methods which make no committment as to the identity of returned instances" rather than providing accessible constructors
  • "Are freely substitutable when equal"

That Java SE 8 documentation also warned that "a program may produce unpredictable results if it attempts to distinguish two references to equal values of a value-based class, whether directly via reference equality or indirectly via an appeal to synchronization, identity hashing, serialization, or any other identity-sensitive mechanism." It further emphasizes, "Use of such identity-sensitive operations on instances of value-based classes may have unpredictable effects and should be avoided." This documentation remains essentially the same today (Java 15). This documentation is linked to from certain identified JDK classes' Javadoc comments. A good overview on value-based classes is available in Nicolai Parlog's blog post "Value-Based Classes."

Although some JDK classes have been identified in their Javadoc as value-based classes since Java 8, JEP 390 aims to provide the annotation @ValueBased for these types of JDK classes. The annotation is a stronger construct than a comment and is specifically intended to be used in conjunction with "warnings about improper attempts to synchronize on instances of such classes." The advantage of such an annotation is that developers can be warned about current uses in their code to synchronize on instances of classes that may one day be unsuitable for such synchronization. Because some likely significant time will pass between the availability of this warning and the actual coversion of these @ValueBased-annotated classes to inline classes (the JEP word this as, "several releases before the migration occurs"), developers will have ample opportunity to change the classes upon which they apply synchronization.

The warning about use of JDK classes for synchronization has benefit in its own right. In addition, I think this JEP is an exciting step toward inline types and the JEP is full of details about inline types. Specifically, the first paragraph of the "Motivation" section of JEP 390 states:

The Valhalla Project is pursuing a significant enhancement to the Java programming model in the form of inline classes. Such classes declare their instances to be identity-free and capable of inline or flattened representations, where instances can be copied freely between memory locations and encoded using solely the values of the instances' fields.

Perhaps the most anticipatory sentence in JEP 390 is in the second paragraph of the "Motivation" section (I've added the highlighting): "The design and implementation of inline classes is sufficiently mature that we can confidently anticipate migrating certain classes in the Java Platform to become inline classes in a future release."

The "Motivation" section of JEP 390 also enumerates the "properties" of a "candidate inline class." These are very simlar to the characteristics of "value-based classes" listed earlier:

  • "Is final"
  • "Declares only final instance fields"
  • "Extends Object, or a hierarchy of abstract classes that declare no instance fields and have no instance initialization logic"
  • "Declares only private constructors, or has deprecated its constructors for removal"
  • "Does not promise a unique instance will be created with each factory-method invocation (or any other instance creation mechanism)"
  • "Does not rely upon or expose object identity through any of its methods"
  • "Overrides toString, equals, and hashCode"
  • "Declares no synchronized methods, and discourages clients from performing synchronization"

JEP 390 also comments on the not-so-surprising closeness of the listed "properties" of "inline classes" to the characteristics of "value-based classes":

The Java Platform API uses the term value-based class to informally describe certain classes that satisfy similar constraints. If a class is described as value-based then it will probably be able to become an inline class.

JEP 390 explains that the @ValueBased annotation "will be introduced to indicate a class, interface, or method result for which no instances are expected to rely on object identity." The annotation is intended to "communicate to developers that they should exercise caution when using == or identityHashCode, and should not perform synchronization."

One of the other sections of JEP 390 that is particularly interesting to me is the section in the "Description" that lists likely "declarations in the Java Platform API and the JDK" to which the @ValueBased annotation may be applied. These include the "primitive wrapper classes" (perhaps one of the categories of most commonly used classes with synchronization that will need to be changed), the "optional classes" such as java.util.Optional, many classes in the java.time API, the java.util "collection factories", and more.

Constructs annotated with @ValueBased annotation will be highlighted in a javac warning. JEP 390 discusses how compile-time and runtime warnings will eventully become errors in some cases as @ValueBased-annotated classes become inline classes:

When a @ValueBased class becomes an inline class, the runtime warnings will be superseded by standard errors. At compile time, synchronization on an inline class type may also trigger a standard error. Compiler warnings will continue to be useful for interface types and method results.

The @ValueBased annotation and associated warnings will help developers to migrate their Java code that uses identified JDK value-based classes in a way not compatible with inline classes to safer alternatives. JEP 390 not only spells out the details of this, but also provides evidence of the growing maturity of the Project Valhalla work on inline classes.

Friday, September 25, 2020

foojay - A Place for Friends of OpenJDK

I welcome new sources of information on all things Java and was excited to see what the foojay site has to offer after Geertjan Wielenga (@GeertjanW) pointed it out to me. In this post I highlight some of the most promising characteristics of this site for this seeking new information on Java-related topics.

The foojay.io About page describes the site's name: "A place for friends of OpenJDK." This relatively new Azul-sponsored community site was highlighted in Simon Ritter's 25 April 2020 post "foojay: A Place for Friends of OpenJDK." In that post, Ritter states:

For Java users and developers who depend on OpenJDK builds, finding the relevant highlights of a given update and how they may improve or otherwise impact deployments is no small task. Separating issues that have significance to a user from others would involve going through hundreds of individual issues. Following each into the Java Bug System to review details, and deducing relevance for one’s environment can be quite a challenge. In practice, few Java users or organizations will go through this level of effort.

This is where foojay's user-focused Java and OpenJDK update descriptions come in.

New Bug Fixes and Features in the OpenJDK

As the Ritter quote above demonstrates, one of the primary objectives of the foojay site is to help Java developers understand the various JDK offerings available based on OpenJDK and the changes, fixes, and new features associated with different versions of OpenJDK.

The following describes how to quickly see new features associated with various versions of OpenJDK on the foojay site:

  • The "What's New in OpenJDK?" is front and center on the Foojay page.
    • Provides overview of new features coming to recent versions of OpenJDK.
    • Click on month/year to see versions of OpenJDK with changes in that month.
    • Click on specific version of OpenJDK for that month/year to see change categories such as "Highlights", "All Issues", "Component View", and "Security / CVE View"
    • Click on category tab to see table representation of changes to that OpenJDK version in that category. This table includes different details depending on the category. The "Foojar Commentary" column of the "Highlights" table provides summary details about a particular bug or feature.

Java Version Almanac

The Java Version Almanac section of the Foojay site presents details on various versions from 1.0 to through JDK 16 as of this writing. There are, of course, generally more details in the later versions than the earlier versions, but it's interesting to see how Java and the JDK have changed. The information presented in Foojay's "Java Version Almanac" section is based upon javaalmanac.io, which "is provided by Java Champions Marc R. Hoffmann and Cay S. Horstmann".

OpenJDK Command Line Arguments

Another interesting section of the Foojay site is the section on OpenJDK's supported command-line arguments. This section shows command-line arguments for each version of OpenJDK from Java 6 through Java 16 as of this writing. This section is derived from Chris Newland's chriswhocodes.com site.

Foojay Blog

Since its inception, the foojay site seems to have broaded its scope to cover additional topics related to use of Java and OpenJDK. There are several well-known Java developers/speakers who write posts for the Foojay blog. These include the following authors (not the entire list):

Although many of the bloggers on the Foojay site have their own blog sites with the same blog posts, there is an advantage to being able to see the headlines and blogs of these different authors in one place without needing to go to each author's individual blog.

Things I Like About the Foojay Site

  • Coverage of different versions of Java and OpenJDK
  • Coverage of different implementations of OpenJDK
  • Community-based with contributions from multiple individuals and sites
  • Blog posts focused on Java-related technologies
    • Even though many of these posts are available on the authors' sites, it's easier to see the headlines in a single location and click on stories of interest.
    • I like the strong focus on Java in these blog posts because Java is still my main programming language.
  • Single location for updates on what's new in the Java and OpenJDK world
    • This Azul-sponsored site nicely complements the Oracle-provided Inside Java site and a Java developer is likely to be aware of most major developments in the Javasphere if regularly reviewing these two sites.
  • Significantly fewer advertisements than many software development aggregation sites
    • Although there are mentions of Azul offerings, they are limited and not the invasive pop-up style advertisements plaguing many software development aggregation sites.
  • Backed by Azul
    • Too many blogs and other sources that I like for new information on Java tend to die out quickly and I have greater confidence in the Azul-sponsored Foojay site being around for a while.
    • The Foojay site's association with Azul means that it is associated with an OpenJDK contributor who provides their own implementation based on OpenJDK and who is a participant in the Java Community Process.

Conclusion

We continue to have exciting new things coming soon to a JDK near us. With the 6-month cadence of new OpenJDK releases, it can be difficult to keep up with so much change. The Foojay site is a great tool for Java developers who want to see the most important details regarding what's coming without needing to delve into bug reports and OpenJDK mailing lists.