Showing posts with label Java 11. Show all posts
Showing posts with label Java 11. Show all posts

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.

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.

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.

Monday, January 21, 2019

Running JAXB xjc Compiler with OpenJDK 11

As described in the post "APIs To Be Removed from Java 11," a JAXB implementation is no longer included with JDK 11. In this post, I look at using the xjc compiler provided with the JAXB (Java Architecture for XML Binding) reference implementation in conjunction with OpenJDK 11 to compile XML schema files into Java classes.

Prior to Java SE 6, developers wanting to use JAXB with a Java SE application needed to acquire a JAXB implementation separately because one was not provided with the Java distribution. A JAXB implementation was included with Java starting with Java SE 6. This was convenient in many cases, but made things a bit more difficult when developers wished to use a newer version or different implementation of JAXB than the one provided with the JDK. When modularity was introduced with OpenJDK 9, the JAXB implementation was moved into the java.xml.bind module and was marked as deprecated for removal. The JAXB implementation was removed altogether with JDK 11. This post looks into using JAXB's xjc compiler with OpenJDK 11.

Because JDK 11 no longer includes an implementation of JAXB, one must be acquired separately. For this post, I will be using version 2.3.0 of the JAXB reference implementation. The JDK version used in this post is JDK 11.0.2 General-Availability Release.

Running the xjc scripts without arguments leads to help/usage being rendered to standard output.

Usage: xjc [-options ...] <schema file/URL/dir/jar> ... [-b <bindinfo>] ...
If dir is specified, all schema files in it will be compiled.
If jar is specified, /META-INF/sun-jaxb.episode binding file will be compiled.
Options:
  -nv                :  do not perform strict validation of the input schema(s)
  -extension         :  allow vendor extensions - do not strictly follow the
                        Compatibility Rules and App E.2 from the JAXB Spec
  -b <file/dir>      :  specify external bindings files (each <file> must have its own -b)
                        If a directory is given, **/*.xjb is searched
  -d <dir>           :  generated files will go into this directory
  -p <pkg>           :  specifies the target package
  -m <name>          :  generate module-info.java with given Java module name
  -httpproxy <proxy> :  set HTTP/HTTPS proxy. Format is [user[:password]@]proxyHost:proxyPort
  -httpproxyfile <f> :  Works like -httpproxy but takes the argument in a file to protect password 
  -classpath <arg>   :  specify where to find user class files
  -catalog <file>    :  specify catalog files to resolve external entity references
                        support TR9401, XCatalog, and OASIS XML Catalog format.
  -readOnly          :  generated files will be in read-only mode
  -npa               :  suppress generation of package level annotations (**/package-info.java)
  -no-header         :  suppress generation of a file header with timestamp
  -target (2.0|2.1)  :  behave like XJC 2.0 or 2.1 and generate code that doesnt use any 2.2 features.
  -encoding <encoding> :  specify character encoding for generated source files
  -enableIntrospection :  enable correct generation of Boolean getters/setters to enable Bean Introspection apis 
  -disableXmlSecurity  :  disables XML security features when parsing XML documents 
  -contentForWildcard  :  generates content property for types with multiple xs:any derived elements 
  -xmlschema         :  treat input as W3C XML Schema (default)
  -dtd               :  treat input as XML DTD (experimental,unsupported)
  -wsdl              :  treat input as WSDL and compile schemas inside it (experimental,unsupported)
  -verbose           :  be extra verbose
  -quiet             :  suppress compiler output
  -help              :  display this help message
  -version           :  display version information
  -fullversion       :  display full version information


Extensions:
  -Xinject-code      :  inject specified Java code fragments into the generated code
  -Xlocator          :  enable source location support for generated code
  -Xsync-methods     :  generate accessor methods with the 'synchronized' keyword
  -mark-generated    :  mark the generated code as @javax.annotation.Generated
  -episode <FILE>    :  generate the episode file for separate compilation
  -Xpropertyaccessors :  Use XmlAccessType PROPERTY instead of FIELD for generated classes

The xjc compiler scripts (bash file and DOS batch file) are conveniences for invoking the jaxb-xjc.jar. The scripts invoke it as an executable JAR (java -jar) as shown in the following excerpts:

  • Windows version (xjc.bat):
    %JAVA% %XJC_OPTS% -jar "%JAXB_HOME%\lib\jaxb-xjc.jar" %*
  • Linux version (xjc.sh):
    exec "$JAVA" $XJC_OPTS -jar "$JAXB_HOME/lib/jaxb-xjc.jar" "$@"

As the script excerpts above show, an environmental variable XJC_OPTS is included in the invocation of the Java launcher. Unfortunately, the JAXB reference implementation JAR cannot be simply added to the classpath via -classpath because running excutable JARs with java -jar only respects the classpath designated within the executable JAR via the MANIFEST.MF's Class-Path (which entry exists in the jaxb-ri-2.3.0.jar as "Class-Path: jaxb-core.jar jaxb-impl.jar").

One way to approach this is to modify the script to use the JAR as a regular JAR (without -jar) and explicitly execute the class XJCFacade, so that the classpath can be explicitly provided to the Java launcher. This is demonstrated for the Windows xjc.bat script:

%JAVA% -cp C:\lib\javax.activation-api-1.2.0.jar;C:\jaxb-ri-2.3.0\lib\jaxb-xjc.jar com.sun.tools.xjc.XJCFacade %*

In addition to the JAXB reference implementation JAR jaxb-xjc.jar, I also needed to include the javax.activation-api-1.2.0.jar JAR on the classpath because the JavaBeans Application Framework (JAF) is a dependency that is also no longer delivered with the JDK (removed via same JEP 320 that removed JAXB).

It's also possible, of course, to not use the XJC scripts at all and to run the Java launcher directly. The script ensures that environment variable JAXB_HOME is set. This environment variable should point to the directory into which the JAXB reference implementation was expanded.

With these changes, the JAXB xjc compiler can be executed against an XSD on the command line using JDK 11.

Thursday, September 27, 2018

A Tale of Two Oracle JDKs

There has been concern recently that Java developers will inadvertently use the wrong Oracle-provided JDK implementation now (as of JDK 11) that Oracle provides builds of the open source OpenJDK and also provides commercial JDK builds based largely on the OpenJDK source.

The table below compares and contrasts the two versions of JDK that Oracle provides (but Oracle won't be the only supplier of JDK builds available for free and/or for support charge). Please keep in mind this represents my best personal understanding of the differences and similarities of Oracle's two offerings; please check with an authoritative source before making decisions regarding which Oracle JDK implementation to use (or even whether to use an Oracle implementation).

JDK Builds from Oracle (https://jdk.java.net/)
Characteristic Oracle OpenJDK Builds Oracle JDK (Java SE Downloads)
Oracle's Descriptions "End users and developers looking for free JDK versions: Oracle OpenJDK offers the same features and performance as Oracle JDK under the GPL license." "Oracle Customers and ISVs targeting Oracle LTS releases: Oracle JDK is Oracle's supported Java SE version for customers and for developing, testing, prototyping or demonstrating your Java applications."
Web Address https://jdk.java.net/11/ https://www.oracle.com/technetwork/java/javase/downloads/jdk11-downloads-5066655.html
License GNU General Public License, version 2, with the Classpath Exception Oracle Technology Network License Agreement for Oracle Java SE
Build Platforms (Binaries) Linux / x64 (tar.gz)
macOS / x64 (tar.gz)
Windows / x64 (zip)
Linux / x64 (tar.gz, deb, rpm)
macOS / x64 (tar.gz, dmg)
Windows / x64 (zip, exe)
Solaris SPARC (tar.gz)
Pay for Production Use No Yes
Oracle Support Select bug fixes and security patches until next JDK version's General Availability release Java SE Subscription
(Support for LTS versions for up to 8 years)
Several Other Paid Support Offerings under "Oracle Customers"
java -version Example openjdk version "11" 2018-09-25
OpenJDK Runtime Environment 18.9 (build 11+28)
OpenJDK 64-Bit Server VM 18.9 (build 11+28, mixed mode)
java version "11" 2018-09-25
Java(TM) SE Runtime Environment 18.9 (build 11+28)
Java HotSpot(TM) 64-Bit Server VM 18.9 (build 11+28, mixed mode)
Required to Accept License Agreement No Yes
Java Flight Recorder Yes Yes
Java Mission Control Yes Yes
Advanced Management Console No Yes
This table represents my personal understanding only; refer to Oracle documentation and OpenJDK documentation for more authoritative information (see "References" below).

There are other implementations of the JDK that will be available as well, with some being free and some requiring payment. I did not discuss those alternatively provided JDKs in this post in order to keep the comparison cleaner and simpler between the "Oracle OpenJDK builds" and the "Oracle JDK builds".

References

Tuesday, September 25, 2018

JDK 11 General Availability

As scheduled, it was announced today that JDK 11 was released for General Availability. Earlier this week, Iris Clark announced the "JSR 384 (Java SE 11) Final Release" and in that same message referenced the final release version of JSR 384, referenced the "Java SE 11 (18.9) Platform JSR (384)" specification page, and concluded, "The 384 EG is now disbanded."

The "JDK 11 General-Availability Release" page provides "production-ready open-source builds of the Java Development Kit, version 11, an implementation of the Java SE 11 Platform under the GNU General Public License, version 2, with the Classpath Exception." That same JDK 11 GA Release page also points to "commercial builds of JDK 11 from Oracle under a non-open-source license" that are "available for a wider range of platforms" and which "can be found at the Oracle Help Center."

The "JDK 11 GA Release" page also provides links to the detailed JDK 11 Release Notes, to the list of features (JEPs) associated with JDK 11, to the Java SE/JDK Version 11 API Specification (Javadoc), and to the Java SE 11 Tools and Command Reference.

In the message "Java 11 / JDK 11: General Availability", Mark Reinhold writes:

JDK 11, the reference implementation of Java 11 and the first long-term support release produced under the six-month rapid-cadence release model[1][2], is now Generally Available. We've identified no P1 bugs since we promoted build 28 over four weeks ago so that’s the official GA release, ready for production use.

JDK 11 is significant for several reasons, not the least of which is its status as the basis for Oracle's LTS offering and the likelihood of this being the version of Java many shops will move to if they are currently on Java 8. An interesting read along these lines for those who do not intend to purchase commercial support for Oracle JDK implementations is "The future of Java and OpenJDK updates without Oracle support." Other recent posts that are probably worth reading in light of JDK 11's release are "Oracle JDK Releases for Java 11 and Later", "Java Is Still Free", and "Introducing Java SE 11".

Oracle's "Java SE Development Kit 11 Downloads" page highlights (in orange-ish background and with emphasized title "Important changes in Oracle JDK 11 License") the "substantially different" Oracle Technology Network License Agreement that now applies to Oracle Java SE and provides a link to "this software under the GPL License on jdk.java.net/11" (Oracle's OpenJDK builds).

Saturday, August 18, 2018

JDK 11: Release Candidate Update and OpenJDK JDK 11 LTS

JDK 11 is scheduled to be released for General Availability on Tuesday, 25 September 2018. A 16 August 2018 Mark Reinhold message on the OpenJDK jdk-dev mailing list announced that "JDK 11 is now in the Release Candidate Phase." However, Reinhold provided updated details in a 17 August 2018 message on that same mailing list in which he stated, "We tagged the first Release Candidate build this morning (jdk-11+27), but since there are some open P1 bugs (http://j.mp/jdk-rc) it's not actually a Release Candidate." Reinhold's last message concluded, "Stay tuned ..."

The early access builds are available under "JDK 11 Early-Access Builds," but the most current version available there as of this writing (18 August 2018) is Build 26 (2018/8/9).

The "JDK 11 Release Candidate Bugs" link provided by Reinhold currently shows two P1 bugs written against JDK 11: JDK-8207317 ["SSLEngine negotiation fail Exception behavior changed from fail-fast to fail-lazy"] and JDK-8209637 ["[s390x] Interpreter doesn't call result handler after native calls"].

"Java 11" is significant from a JDK and Java SE perspective. The "Oracle Java SE Support Roadmap" states:

Beginning with Oracle Java SE 11 (18.9 LTS), the Oracle JDK will continue to be available royalty-free for development, testing, prototyping or demonstrating purposes. As announced in September 2017, with the OracleJDK and builds of Oracle OpenJDK being interchangeable for releases of Java SE 11 and later, the Oracle JDK will primarily be for commercial and support customers and OpenJDK builds from Oracle are for those who do not want commercial support or enterprise management tools.

The statement quoted above tells us that the OpenJDK and Oracle JDK are intended to be completely interchangeable as of Java SE 11. We also see that, as of Java SE 11, Oracle JDK distributions are intended for Oracle's commercial and support customers and the OpenJDK distributions are for those who do not want Oracle customer support or Oracle enterprise management tools. However, Oracle JDKs can still be used without payment for development, testing, prototyping, and demonstrations.

The "Oracle Java SE Support Roadmap" provides further details on how long Oracle support will be provided for the versions of Oracle JDK. The "Long-Term-Support (LTS) releases" are released every three years and Oracle Java SE 11 is the first such LTS version. Once Oracle Java SE 11 is released, Oracle will not provide commercial support for non-LTS Oracle SE 10.

"Oracle Java SE Support Roadmap" spells out details related to Oracle JDKs, but does not provide the same level of details related to the OpenJDK JDKs. In a message on the OpenJDK jdk-dev mailing list, Stephen Colebourne asks, "What does LTS mean for OpenJDK?" In that message, Colebourne presents some interesting questions and provides links to background references. The replies to his message provide some new details on OpenJDK JDK 11 support.

One of the responses to Colebourne's message is from Mark Reinhold. Reinhold writes that Oracle will provide "at least six months of free, GPL-licensed updates with binaries at http://jdk.java.net" for OpenJDK JDK 11. Reinhold also clarifies the the purpose of the http://jdk.java.net site: "The jdk.java.net site is for builds from Oracle, under various licenses FLOSS and otherwise. It's not part of the OpenJDK Community. Other implementors have their own distribution sites or related mechanisms."

Andrew Haley also responds to Colebourne's message and writes that "I'll say what I can" until "a public statement" can be made (presumably from Red Hat). Haley does state, "Red Hat is committed to support OpenJDK for its customers for some time. Our policy for current versions can be seen at https://access.redhat.com/articles/1299013#OpenJDK_Lifecycle_Dates_and_RHEL_versions." The provided link presents the question "Is Red Hat releasing OpenJDK 9, 10, or 11?" and answers that question, "Red Hat will skip Java SE 9 and 10, and ship an OpenJDK distribution based on Java SE 11. See the Red Hat OpenJDK 11 Advice article for additional information." Haley adds, "Given that Red Hat has an upstream first policy, we will make sure that all security patches are applied to upstream OpenJDK releases and our builds are TCK'd."

Martijn Verburg's response to Colebourne's questions is from the perspective of AdoptOpenJDK. Verburg writes that "AdoptOpenJDK offered to build, test and make available OpenJDK LTS binaries for the major (and several minor) platforms." He adds the "extra statements" that "AdoptOpenJDK will not offer commercial support" and that "AdoptOpenJDK ... will not be backporting patches."

The scheduled release of OpenJDK JDK 11 is just over a month away. With Oracle's "Commercial User End of Public Updates" for JDK 8 scheduled for January 2019, it's likely that many organizations will desire to move to JDK 11 by then.

Wednesday, August 8, 2018

APIs To Be Removed from Java 11

After seeing several APIs removed as part of Java 10, Java 11 (JSR 384) looks to remove some more APIs. In the recent OpenJDK java-se-spec-experts mailing list post "JSR 384 (Java SE 11) PFD Specification posted to jcp.org," Iris Clark announced the availability of the Java SE 11 (18.9) Proposed Final Draft Specification. This document lists APIs that are being removed as part of Java 11.

"Individual" (class/method) APIs are being removed in addition to the removal of entire modules.

Individual APIs Being Removed in JDK 11
Class/Method Being RemovedAdditional Notes / References
java.lang.Runtime.runFinalizersOnExit(boolean) Dangerous runFinalizersOnExit
Deprecating Java's Finalizer
java.lang.SecurityManager.checkAwtEventQueueAccess() Security Managers and Java SE JDK
JDK-8177554
JDK-8029886
JDK-8186535
java.lang.SecurityManager.checkMemberAccess(java.lang.Class,int)
java.lang.SecurityManager.checkSystemClipboardAccess()
java.lang.SecurityManager.checkTopLevelWindow(java.lang.Object)
java.lang.System.runFinalizersOnExit(boolean) Dangerous runFinalizersOnExit
Deprecating Java's Finalizer
java.lang.Thread.destroy() Thread Methods destroy() and stop(Throwable) Removed in JDK 11
java.lang.Thread.stop(java.lang.Throwable)

Module-level APIs Being Removed from Java 11
Name Module Removed Potential Third Party Replacement
JavaBeans Activation Framework (JAF) java.activation Maven Artifact
Common Object Request Broker Architecture (CORBA) java.corba glassfish-corba
Aggregator Module for other modules listed in this table java.se.ee  
Java Transaction API (JTA) java.transaction Maven Artifact
Java Architecture for XML Binding (JAXB) java.xml.bind Maven Artifact
Java API for XML Web Services (JAX-WS) java.xml.ws Maven Artifact
Common Annotations java.xml.ws.annotation Maven Artifact

JEP 320 ["Remove the Java EE and CORBA Modules"] and the StackOverflow page "Replacements for deprecated JPMS modules with Java EE APIs" provide significantly more details on replacements for the Java SE EE/CORBA-related modules.

Wednesday, August 1, 2018

JDK 11: Taking Single-File Java Source-Code Programs Out for a Spin

The JDK 11 Early Access Builds include functionality related to JEP 330 ("Launch Single-File Source-Code Programs"). I have written about JEP 330 before in posts "Shebang Coming to Java?" and "JEP 329 and JEP 330 Proposed for JDK 11". I get to take this feature out for a spin in this post thanks to the JDK 11 Early Access Builds.

For this demonstration, I'm using the latest (as of this writing) OpenJDK JDK 11 Early Access Build 24.

One of the first indications that support for JEP 330 is included with this JDK distribution is seen when using the -help flag (java -help):

As shown in the last image, the "help" starts with a "usage" statement and the last example in the usage statement describes how to use the Java launcher (java) to run single-file source-code programs. Specifically, the output shows the following "usage" with the usage that is the subject of this post highlighted here:

Usage: java [options] <mainclass> [args...]
           (to execute a class)
   or  java [options] -jar <jarfile> [args...]
           (to execute a jar file)
   or  java [options] -m <module>[/<mainclass>] [args...]
       java [options] --module <module>[/<mainclass>] [args...]
           (to execute the main class in a module)
   or  java [options] <sourcefile> [args]
           (to execute a single source-file program)

To demonstrate this feature, I'm going to use a simple example adapted (very slightly) from that provided in the 24 May 2018 Mario Torre post on the OpenJDK jdk-dev mailing list.

helloYou.jv

#!/bin/java
public class Hello
{
   public static void main(final String[] args)
   {
      final String name = System.console().readLine("\nPlease enter your name: ");
      System.console().printf("Hello, %s!%n", name);
   }
}

I have called this file helloYou.jv. Note that it does NOT end with the .java extension that regular Java source code files end with and I did not match the name of the file to the name of the class. In fact, I started the file's name with a lower case letter!

When I try to run this file directly with OpenJDK 11 EA-24, I see an error ("Could not find or load main class helloYou.jv"):

The following screen snapshot demonstrates that it works when I pass the flag --source=11 to the Java launcher.

I highlighted in my post "Shebang Coming to Java?" that it sounded like single-file source programs used with this JEP 330 support were not going to be allowed to end with the .java extension (which extension would be reserved for traditional Java source files). This seems to be the case as shown in the next screen snapshot where I attempt to run this feature against the same code as above, but now with the file name helloYou.java.

The last image demonstrates that we cannot run .java files with a shebang because they are treated as regular Java files and thus must meet the specification of regular Java source code files.

With this early access build, if I comment out the shebang line, I can run the now traditionally compliant Java single source code file helloYou.java (even with the .java extension and without the flag --source=11).

Had I attempted that last maneuver with OpenJDK JDK 10, attempting to run a Java source code file like that just shown would produce the error message discussed earlier: "Error: Could not find or load main class helloYou.java". I would have needed to compile the Java source code file into a .class file and would have needed to make sure it was on the classpath of the Java launcher.

This post has been a first look at the feature single-file source-code programs that is now available in the JDK 11 Early Access Builds.

Monday, July 23, 2018

JDK 11: New Default Collection Method toArray(IntFunction)

The "JDK 11 Early-Access Release Notes" indicate that Early Access Build 20 of JDK 11 includes a new default method on the Collection interface that "allows the collection's elements to be transferred to a newly created array of a desired runtime type". This new default method [Collection.toArray(IntFunction)] works similarly to the same-named method already available on the Stream interface [Stream.toArray​(IntFunction)].

The next code listing demonstrates this new JDK 11 default Collection method in action (on a Set in this case).

final Set<String> names = Set.of("Fred", "Wilma", "Barney", "Betty");
out.println(Arrays.toString(names.toArray(String[]::new)));

Because I used an (unordered) Set, order of the Strings in the generated array can be different than the order the Strings were specified for initialization of the Set. This is demonstrated in the next screen snapshot (which also indicates that I'm using JDK 11 Early Access Build 23 for this example).

Many of us use Java collections more frequently than arrays, but there are times we need to convert these collections to arrays. The default method Collection.toArray(IntFunction) provides a highly convenient mechanism for this. There was already a similar method on Collecton [Collection.toArray(T[])] and the existence of these two methods means it's no longer possible to pass null to either Collection.toArray(-) method (compiler is unable to distinguish them and will report the error message "reference to toArray is ambiguous"). This is not much of a price to pay as both methods throw NullPointerException anyway when null is passed to them.

Saturday, July 21, 2018

Optional.isEmpty() Available in JDK 11 EA Builds

My recently posted question "Optional.isEmpty() Coming to Java?" was prompted by a core-libs-dev mailing list post titled "RFR: 8184693: (opt) add Optional.isEmpty". The current JDK 11 Early Access builds (such as OpenJDK JDK Early Access Build 23 that I use in this post) now include the isEmpty() method on the "Optional" classes Optional, OptionalDouble, OptionalInt, and OptionalLong. This allows for more fluent expression in cases that formerly relied upon negation of Optional.isPresent() [or !OptionalDouble.isPresent(), !OptionalInt.isPresent(), or !OptionalLong.ifPresent()] as was done previously.

The next simple and contrived code listing demonstrates Optional.isEmpty().

public static void demonstrateOptionalIsEmpty()
{
   final Optional<String> middleName = getMiddleName();
   if (middleName.isEmpty())
   {
      out.println("There is no middle name!");
   }
}

Although the same functionality that Optional.isEmpty() provides can be achieved with !Optional.isPresent(), there are advantages to having these types of "isEmpty" methods available in the APIs of commonly used collection and data-holding classes. The ! symbol is more easily missed when reading and reviewing code than is an explicitly named method such as "isEmpty()." Having such a method also aligns Optional's API for detecting "empty" more closely to that provided by String [String.isEmpty()],Collection [Collection.isEmpty()], and Map [Map.isEmpty()].

Thursday, July 5, 2018

Applying New JDK 11 String Methods

In the posts "New Methods on Java String with JDK 11" and "String#repeat Coming to Java?", I discussed six new methods coming to the Java String with JDK 11. The available early access JDK 11 builds already include these new methods and I use one of those early access builds to demonstrate them in this post.

I am using OpenJDK JDK 11 Early Access Build 20 for compiling and running the examples shown in this post.

The six methods added to String for JDK 11 that are demonstrated in this post via the OpenJDK JDK 11 Early Access Build 20 are:

  • String.repeat(int)
  • String.lines()
  • String.strip()
  • String.stripLeading()
  • String.stripTrailing()
  • String.isBlank()

The source code for the examples demonstrated in this post is available on GitHub.

String.repeat(int)

The String.repeat(int) method provides handy functionality that I've wished to see in Java since experiencing this functionality in Groovy. As its name suggests, this method repeats the String it is run against as many times as provided by the int parameter. I will likely use this method frequently in the future when generating simple demonstrations and use it for this post's examples. The next code listing demonstrates use of String.repeat(int) to easily generate header separators for the demonstration output.

Use of String.repeat(int)

/**
 * Write provided {@code String} in header. Note that this
 * implementation uses {@code String.repeat(int)}.
 *
 * @param headerText Title of header.
 */
private static void writeHeader(final String headerText)
{
   final String headerSeparator = "=".repeat(headerText.length()+4);
   out.println("\n" + headerSeparator);
   out.println("= " + headerText + " =");
   out.println(headerSeparator);
}

The writeHeader(String) method uses String.repeat(int) to easily generate "header separator" lines from the "=" character enough times to cover the provided headerText length plus 4 additional characters to allow for an extra "=" and extra space on each side of the "header text". The writeHeader(String) method is used by all the other demonstration examples in this post and so will be demonstrated via those examples.

String.lines()

The String.lines() method splits the String upon which it is called by its line terminators and returns a Stream of Strings as demarcated by those line terminators.

Use of String.lines()

/**
 * Demonstrate method {@code String.lines()} added with JDK 11.
 */
public static void demonstrateStringLines()
{
   final String originalString = prepareStringWithLineTerminators();
   final String stringWithoutLineSeparators
      = originalString.replaceAll("\\n", "\\\\n");
   writeHeader("String.lines() on '"  + stringWithoutLineSeparators  + "'");
   final Stream<String> strings = originalString.lines();
   strings.forEach(out::println);
}

Sample output is shown in the next screen snapshot.

String.strip() / String.stripLeading() / String.stripTrailing()

The String.strip(), String.stripLeading(), and String.stripTrailing() methods trim white space [as determined by Character.isWhiteSpace()] off either the front, back, or both front and back of the targeted String.

Use of String.strip() / String.stripLeading() / String.stripTrailing()

/**
 * Demonstrate method {@code String.strip()} added with JDK 11.
 */
public static void demonstrateStringStrip()
{
   final String originalString = prepareStringSurroundedBySpaces();
   writeHeader("String.strip() on '" + originalString + "'");
   out.println("'" + originalString.strip() + "'");
}

/**
 * Demonstrate method {@code String.stripLeading()} added with JDK 11.
 */
public static void demonstrateStringStripLeading()
{
   final String originalString = prepareStringSurroundedBySpaces();
   writeHeader("String.stripLeading() on '" + originalString + "'");
   out.println("'" + originalString.stripLeading() + "'");
}

/**
 * Demonstrate method {@code String.stripTrailing()} added with JDK 11.
 */
public static void demonstrateStringStripTrailing()
{
   final String originalString = prepareStringSurroundedBySpaces();
   writeHeader("String.stripTrailing() on '" + originalString + "'");
   out.println("'" + originalString.stripTrailing() + "'");
}

When the above code is executed, the output looks like that shown in the next screen snapshot.

String.isBlank()

The String.isBlank() method indicates if the targeted String is empty or contains only white space characters as determined by Character.isWhitespace(int).

Use of String.isBlank()

/**
 * Demonstrate method {@code String.isBlank()} added with JDK 11.
 */
public static void demonstrateStringIsBlank()
{
   writeHeader("String.isBlank()");
   final String emptyString = "";
   out.println("Empty String -> " + emptyString.isBlank());
   final String onlyLineSeparator = System.getProperty("line.separator");
   out.println("Line Separator Only -> " + onlyLineSeparator.isBlank());
   final String tabOnly = "\t";
   out.println("Tab Only -> " + tabOnly.isBlank());
   final String spacesOnly = "   ";
   out.println("Spaces Only -> " + spacesOnly.isBlank());
}

An example of executing this code is shown in the next screen snapshot.

Some of the methods whose code is shown above call "helper" methods that can be seen on GitHub.

The methods added to JDK 11's String are small additions, but will make certain "presentation" tasks related to Java Strings easier than in the past and reduce the need for third-party libraries.