Skip to main content

Java Notable Features

Java 9 All you need to know While many of us are still struggling to convince our colleagues to use Java 8 lambdas and Joda time apis, Oracle released two new versions of JDK (9 and 10/18.3). It doesn't mean that we need to migrate to new JDK immediately but it is worthwhile to take a look at functionality available in them. Two important change happened in JDK release plan. Oracle is going to release new JDK version every 6 months now and a long term support (LTS) version every 3 years. The end of non commercial public updates for Java 8 is September 2018 (same time frame when JDK 11 or 18.9 will be out). Oracle is trying to push customers towards what it calls "Java SE Advanced" in order to receive critical bug fixes. You can find more details about JDK release schedule in this video.

Java 9 was never meant to be used in production but for developers to brace for the JigSaW project impact. If you are looking to upgrade JDK in your application and your current version is 8 then I would suggest you wait untill JDK 18.9 (September 2018) while encouraging developers to keep up to date with new features being added since Java 8. JDK 9.04 was final release for JDK 9 and since JDK 10 or 18.3 was released in March 2018, people will most likely fly over Java 9. The public availability of Java SE 8 updates from Oracle has been extended to at least January 2019.  Moreover, Oracle will continue to provide consumers with updates for personal (non-corporate) use of Java SE 8 through at least the end of 2020.

One important advice from Oracle is that even though you are not upgrading to every 6 month release cadence, you should definitely test your application or services with every version and lookout for deprecated api's. It will save large amount of cleanup when you are ready to upgrade. If you are a library provider, then it is worth the pain to follow the java release candidates and publish compatible version of the library.

New Features in Java 9 and 10  - Here is quick summary of items added or changed in Java 9 and 10.

Jshell

The Java REPL (read-eval-print-loop) shell is a useful tool for coding demostrations, teaching and prototyping. Java is not a scripting language like JavaScript but jdk.shell.spi package provides an execution engine which makes it possible to start with expression in JShell. jdk.jshell.tool can be used to launch jshell programmatically, specially useful in a self-learning practice or tests. I wish oracle would have married sql world with jshell in an interactive shell environment (similar to IBM jsqsh), where sql statements could be implicitly wrapped in JDBC statements and executed. I am hopeful that there would be soon multiple variants of jshell for specific purposes. Even one day probably linux shells like ksh and bash would be accept embedded java code in customized shell environment. Best use of JShell for people like me is writing functions just as you would do in R shell.

  • While using jshell, you can skip semicolon and ignore some exception in java snippets.
  • Statements with backslash (/) in jshell are jshell commands (e.g /exit).
  • Ctrl+u or Ctrl+l can be used to clear screen. in jShell.
  • Similar to linux shells, you can use arrows and /! for previous statements. /imports gives imports in current environment.
  • JShell also supports code help with dot (.) and tab. Tab twice gives javadoc snippet.
  • /list is used to see all statements used before and /save filename saves the script in jshel to a file.
  • To check all commands available in JShell check oracle doc here.
 

Modularity

Modularity in JDK 9 is part of project Jigsaw. Modular development in JDK9 is explained in this javaone video. Essentially you start with jdeps tool to analyze your class dependencies across application jars. Modules help manage package dependencies which are useful for applications consisting of large number of packages. Modules are higher abstraction for packages using exports directive. module-info.java defines while public classes are accesible outside packages in the form of exported packages. This puts additional limitation on public class modifier. If you see a class defined with public keyword, you need to find out the exposure of that class by means of public within module, friend modules or everyone.

You might find it hard to hand type all exports and requires. Luckily eclipse oxygen 2+ or IntelliJ IDEA auto generates module-info.java for you.

 
module codeshare {
exports com.mydevco.lambda.util;
exports com.mydevco.lambda.common;
exports com.mydevco.lambda.model;
exports com.mydevco.javabp.io;
exports com.mydevco.javabp.datetime;
exports com.mydevco.lambda.commandpattern;
exports com.mydevco.lambda.strategypattern;
exports com.mydevco.lambda.unittesting;
exports com.mydevco.javabp.language;
exports com.mydevco.javabp.serialization;

requires commons.csv;
requires commons.io;
requires commons.lang3;
requires commons.logging;
requires commons.math3;
requires java.desktop;
requires java.logging;
requires json.simple;
requires junit;
requires poi;
}

It allows for tight coupling inside the module and loose coupling between the modules. Module path is like class path but you specify directories with jars instead of jar files individually. Module dependency resolution ensures there is no cyclic dependencies and split packages (also known as classpath hell). However migrating to module based system is not possible overnight. It needs to be well planned in advance specially when you are using multiple libraries which are not modularized. You might want to start with JDK itself which is modularized since 9 onwards. There is automatic module inference based on jar names (hyphen replaced with dot) which is helpful feature bridging the classpath with module path. This will remain quirky for possibly some more time. Docker containers have incentive to modularize for serverless application to decrease the size and bundle minimal openjdk. JDK 8 included a tool called jdeps which will help you with module declarations. Book reference for modularity : Java 9 modularity
 

  • Require Module Export Package.
  • Java compiler puts java.base requires for every java application so you don't have to put it in module-info.
  • Java EE modules (e.g java.xml.ws, java.activation, java.transaction etc.) are deprecated. On a side note Java EE itself is moved from oracle to eclipse under new name Jakarta EE.
  • JDK 9 temporarily allows access to internal packages (e.g com.sun.crypto.provider.jce keystore) with a runtime warning. Many projects use these internal packages and they will have to find alternatives.
  • You may need to upgrade tools and libraries to JDK 9 aware versions. This is the largest blocking factor for migrating to JDK 9.
  • No matter whether you ready to migrate to JDK 9 or 10, please run jdeps to understand where the migration would lead to.
  • Signed jars will have different set of problems to worry about. Gui base policytool is deprecated. jarsigner and keytool are commandline tools still available in jdk 10.
  • If you writing services which provide implementation of an interface under different module than interface then you will need to utilize "uses" and "provides" directives. More details about modules and services best practices can be found here.
  • module is a keyword but only in module-info.java. You can use module in java program elsewhere.
  • javac -p is used for module path.
  • export and require should go hand in hand. Missing require will give compilation error.
  • jlink can be used customize JRE to the bare minimal required for our packages.
    jlink --module-path <modulepath> --add-modules <modules> --limit-modules <modules> --output <path>

 

Garbage Collection

Garbage First (G1), introduced in JDK 7 is capable of time slicing and gives more control on GC pauses target (-XX:MaxGCPauseMillis). Until Java 10, G1 was single threaded but in JDK10 you can define the number of parallel thread for G1 by means of -XXParallelGCThreads and -XX:G1ConcRefinementThreads. Parallel Full GC for G1 is improved by making it parallel hence reducing  latencies.

  • String memory optimization using string deduplication. G1 now checks for common string content and points them to a common location. This is one step beyond bytecode optimization which could be achieved through string.intern. It adds some overhead in concurrent sweep cycles but saves memory and collection time. Refer to JEP 192 for more details.

  • Use -XX: ClassUnloadingWithConcurrentMark to reduce the need for Full GC as it loads and unloads unused classes.

  • Copying humongeous objects (objects larger than half the region) across young, eden and survivor regions are expensive. G1 can eagerly reclaim such objects without waiting for concurrent mark cycle.

  • One of the common problem faced while optimizing garbage collection in production is to decide when the CMS cycle should kick in based on the percentage of old generation occupied. JDK 9 got little smarter in this regard as now it samples runtimetime garbage collection cycles adapts to determine optimal cycles start time. It is controlled with -XX:G1UseAdaptiveIHOP jvm flag and enabled by default

  • G1 is now default garbage collector in JVM. Applications, which does not select specific garbage collector should test the impact of G1 in production like environment. The reason to push G1 on the forefront is well explained here and tips for tuning G1 can be found in this InfoQ and Oracle articles.

  • For G1, the regions need not be contiguous and need not be distinct (young or old/tenured). Young gc region is collected during each pause incrementally which is made up of Survivor and Eden. Regions with most garbage collected first. More details about collection and evacuation can be found at https://plumbr.io/handbook/garbage-collection-algorithms-implementations#g1

  • When there are no more free regions to promote to the old generation or to copy to the survivor space, and the heap cannot expand since it is already at its maximum, an evacuation failure occurs. Evacuation failure is something you need to worry about while using or defaulting to G1. To troubleshoot evacuation time of young or old regions enable gc+ergo+cset=trace log output and check predicted timings for each region type.

  • To reduce the amount of humongous object fragmentation, you may utilize -XX:G1HeapRegionSize

  • If reference object processing takes too long try out -XX:+ParallelRefProcEnabled. If young only collection takes too long use -XX:G1NewSizePercent and -XX:G1MixedGCCountTarget.

  • Most importantly do not use -Xmn or -XX:NewRatio as they limit young generation size. The premise of G1 to is to meet the pause-time goals and it can not do so if these options override the young generation size to fixed value.

sun.misc.unsafe

If your application or any of the dependent libraries are using sun.misc.unsafe then you should be aware of the following (based on JEP260 and JEP193)

  • Its available to code in the classpath of code within module by request.
  • module declaration of unsupported
  •  
    module jdk.unsupported {
        exports sun.misc;
        exports sun.reflect;
        exports com.sun.nio.file;
     
        opens sun.misc;
        opens sun.reflect;
    }
     
     
  • To use it in your module, add the following in your module declaration
     
     module myunsafemodule {
         requires jdk.unsupported;
     }
     
     
  • Following method would give handler to unsafe
     
     @SuppressWarnings("restriction")
        private static Unsafe getUnsafe() throws NoSuchFieldException, IllegalAccessException {
            Field singleoneInstanceField = Unsafe.class.getDeclaredField("theUnsafe");
            singleoneInstanceField.setAccessible(true);
            return (Unsafe) singleoneInstanceField.get(null);
        } 
     

Collections

There are plenty of new features in Collections framework both obvious ones and some very subtle ones made to help G1 make better garbage collection choices.

  • Finally there is nice built in support for unmodifiable collections instead of programmers trying to write collection wrappers masking put and add methods or using CopyOnWrite flavors. Most of the collection interface now has utility function added to create instance of those collections using elements. These methods are in the form [Interface].of(elements ..). e.g List<Integer>.of(1,2,3,4,5) will create List<Integer>. It saves some boilerplate code. Map.of("Key1","Value1,"Key2","Value") will create unmodifiable map.

Concurrency

  • CompletableFuture now has completeOnTimeout method to return some error value on timeout. Alternately you can use .orTimeout on CompletableFuture.
  • CompletableFuture can be copied. copy method is similar to thenApply with same value. It can used as a defensive copy to prevent clients from completing.

Lambda

  • Stream had filter, limit and skip methods available in Java 8. Jva 9 has conditional equivalent of limit and skip. They are called takeWhile and dropWhile.  They are equivalent of using break and while in for loop.
  • Stream.iterate is futher enhanced in Java9 to mimic traditional for(int i =0; i< max; i++) style of for loop.
  • get orElse methods in stream can be replaced with Optional ifPresentElse
  • Map method in Optional is also smarter now. You can use or method in Optional to supply alternate value when optional is empty.
  • Optional can also be converted to stream now, it will always have at most one value.
  • StackWalker stream can be used to iterate through stacktrace as stream. StackWalker.getInstance can be used with different options such as RETAIN_CLASS_REFERENCE, SHOW_REFLECT_FRAMES or SHOW_HIDDEN_FRAMES. You can use forEach or walk method to iterate through stack frames. Dealing with StackElements is time consuming process and should not be used in production environment. It can be part of troubleshooting mechanism when something bad happens.

Reactive programming using Flow api

JDK 9 supports basic reactive programming confirming the Reactive stream specification. The reactive manifesto, published in 2014, provides base minimal attributes of a reactive system as responsive, resilient, elastic and message driven. Reactive programming model is based on the fact that scalability can be achieved with limited number of threads with the help of these four components:

1. Publisher publishes stream of data. Stream is a memory efficient data pipe line. It can be backed by a buffer or queue but the boiler plate logic is hidden from the user.

2. Subscriber subscribes and accepts items from the stream asynchronously. Its the responsibility of subscriber to request for items until onComplete is invoked or some exception occurs in the publisher/subscriber communication.

3. Processor implements both publisher and subscriber and can act as intermediary between the two. Processor can receive the item, filter the item or process the item and then pass on to the relevant subscriber.  Processor has list of subscribers attached.

4. Back Pressure is a mechanism by which application can deal with overflow of items in the reactive systems. This could occur when publisher is publishing items faster than subscriber could process. There are multiple ways a back pressure system could be implemented but it is not part of standard JDK 9 Flow api yet. You may refer to Spring or Rx Java for enabling back pressure in reactive system.

The important point to note here is that the underlying concurrency model is hidden from the application and you don't have to use concurrency api. However SubmissionProcessor has a consume method which can return CompletableFuture. Also SubmissionProcessor can be configured with user supplied Executor for async delivery to subscribers and maximum buffer size for each subscriber. This is purely JDK 9 flavor of a processor and not part of reactor specification. Let's take a look at a working example of Reactive program.


package practice.concurrent.reactive;

import java.util.Arrays;
import java.util.concurrent.Flow.Publisher;
import java.util.concurrent.Flow.Subscriber;
import java.util.concurrent.SubmissionPublisher;

public class MyPublisher implements Publisher {

 private SubmissionPublisher publisher;

 public MyPublisher() {
  this.publisher = new SubmissionPublisher();
 }

 @Override
 public void subscribe(Subscriber subscriber) {
  // Register Subscriber
  publisher.subscribe(subscriber);
 }

 public void publish(T... items) {
  // Publish items
  if (null != publisher) {
   System.out.println(Thread.currentThread().getName() + " Publishing Items...");
   Arrays.asList(items).stream().forEach(i -> publisher.submit(i));
   try {
    Thread.sleep(1000);
   } catch (InterruptedException e) {
    // TODO Auto-generated catch block
    e.printStackTrace();
   }
   publisher.close();
   System.out.println(Thread.currentThread().getName() +" Publisher Done");
   
  }
 }

}


package practice.concurrent.reactive;

import java.util.concurrent.Flow.Subscriber;
import java.util.concurrent.Flow.Subscription;

public class MySubscriber implements Subscriber {
 private String subscriberName;
 private Subscription subscription;

 public MySubscriber(String subscriberName) {
  this.subscriberName = subscriberName;
 }

 @Override
 public void onSubscribe(Subscription subscription) {
  this.subscription = subscription;
  subscription.request(1); // a value of Long.MAX_VALUE may be considered as effectively unbounded
 }

 @Override
 public void onNext(T item) {
  System.out.println(Thread.currentThread().getName() + " " + subscriberName + " Got : " + item);
  subscription.request(1); // a value of Long.MAX_VALUE may be considered as effectively unbounded
 }

 @Override
 public void onError(Throwable t) {
  t.printStackTrace();
 }

 @Override
 public void onComplete() {
  System.out.println(Thread.currentThread().getName() + " " + subscriberName + " Done ");
 }
}


  • 
    package practice.concurrent.reactive;
    
    import java.util.concurrent.Flow.Processor;
    import java.util.concurrent.Flow.Subscription;
    import java.util.concurrent.SubmissionPublisher;
    import java.util.function.Function;
    
    public class MyTransformProcessor extends SubmissionPublisher implements Processor {
    
     private final Function function;
     private Subscription subscription;
     private final String processorName;
    
     public MyTransformProcessor(Function function, String processorName) {
      super();
      this.function = function;
      this.processorName = processorName;
     }
     
    
     @Override
     public void onSubscribe(Subscription subscription) {
      (this.subscription = subscription).request(1);
     }
    
     @Override
     public void onNext(T item) {
      System.out.println(Thread.currentThread().getName() + " " + processorName + " Got Item: " + item);  
      subscription.request(1);
      submit((R) function.apply(item));
     }
    
     @Override
     public void onError(Throwable t) {
      t.printStackTrace();
     }
    
     @Override
     public void onComplete() {
      close();
      System.out.println(Thread.currentThread().getName() + " " + processorName + " Done ");  
     }
    }
    
    
  • 
    package practice.concurrent.reactive;
    
    public class ReactorTest {
    
     public static void main(String[] args) {
      //Create Publisher  
         MyPublisher publisher = new MyPublisher<>();
         
         MySubscriber stringSubscriber = new MySubscriber<>("X subscriber");
         MySubscriber numberSubscriber = new MySubscriber<>("Number subscriber");
       
         //Create Processor and Subscriber  
         MyTransformProcessor stringProcessor = new MyTransformProcessor<>(s -> {
          if (s.equals("x")) {
           return "Got x";
          }
         return "Not x";
         }, "X Processor");
    
      MyTransformProcessor numberProcessor = new MyTransformProcessor<>(s -> {
       if(s.matches("\\d+")) {
        return Double.parseDouble(s);
       }
       return Double.NaN;
      }, "Number Processor");
       
         //Chain Processor and Subscriber
         stringProcessor.subscribe(stringSubscriber);
         numberProcessor.subscribe(numberSubscriber);
    
         publisher.subscribe(stringProcessor);  
         publisher.subscribe(numberProcessor);  
    
       
         String[] items = {"1.9", "x", "2.5", "y", "3.7", "x","z","10", "20", "500"};
         publisher.publish(items);
         
         //You Can skip processor and just use publisher and subscriber as well
         //Create another Publisher  
         MyPublisher publisher1 = new MyPublisher<>();
         
         MySubscriber stringSubscriber1 = new MySubscriber<>("X subscriber");
         
         publisher1.subscribe(stringSubscriber1);
         publisher1.publish(items);
     }
    }
    
    
  • HTTP/2

    HTTP 2 client is an incubator project included in jdk. To include HTTP 2 client in your module use "requires jdk.incubator.httpclient;" in your module-info. It has both synchronous as well as asynchronous method to send/post request and recive HttpResponse. SendAsync returns CompletableFuture. HttpResponse.BodyHandler can also copy response into a file using asFile method. You can use HttpRequest.BodyPublisher to post data.

    
    package practice;
    
    import jdk.incubator.http.HttpClient;
    import jdk.incubator.http.HttpRequest;
    import jdk.incubator.http.HttpResponse;
    
    import java.net.URI;
    import java.util.List;
    import java.util.concurrent.CompletableFuture;
    import java.util.concurrent.TimeUnit;
    import java.util.stream.Collectors;
    
    public class HttpClientExample {
        public static String getNow(String uri) throws Exception {
            HttpClient client = HttpClient.newHttpClient();
            HttpRequest request = HttpRequest.newBuilder()
                    .uri(URI.create(uri))
                    .build();
    
            HttpResponse response =
                    client.send(request, HttpResponse.BodyHandler.asString());
    
            return response.body();
        }
    
        public static String getLater(String uri) throws Exception {
            HttpClient client = HttpClient.newHttpClient();
            HttpRequest request = HttpRequest.newBuilder()
                    .uri(URI.create(uri))
                    .build();
    
            CompletableFuture future = client.sendAsync(request, HttpResponse.BodyHandler.asString()).thenApply(HttpResponse::body);
            return future.get(5000,TimeUnit.MILLISECONDS);
      }
        public static void getConcurrent(List uris) {
            HttpClient client = HttpClient.newHttpClient();
            List requests = uris.stream()
                    .map(HttpRequest::newBuilder)
                    .map(reqBuilder -> reqBuilder.build())
                    .collect(Collectors.toList());
    
            CompletableFuture.allOf(requests.stream()
                    .map(request -> client.sendAsync(request, HttpResponse.BodyHandler.asString()))
                    .toArray(CompletableFuture[]::new))
                    .join();
        }
    
    
        public static void post(String uri, String data) throws Exception {
            HttpClient client = HttpClient.newBuilder().build();
            HttpRequest request = HttpRequest.newBuilder()
                    .uri(URI.create(uri))
                    .POST(HttpRequest.BodyPublisher.fromString(data))
                    .build();
    
            HttpResponse response = client.send(request, HttpResponse.BodyHandler.discard(""));
            System.out.println(response.statusCode());
        }
    
        public static void main(String[] args) {
            try {
                final String uri = "http://www.mydeveloperconnection.com/p/introduction-to-hash.html";
                final String uri1 = "http://www.mydeveloperconnection.com/p/java_14.html";
                System.out.println(getNow(uri));
                System.out.println(getLater(uri1));
                getConcurrent(List.of(new URI(uri), new URI(uri1)));
                post("http://form/post", "Good Day");
    
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }
    
    

    Other Language Enhancements

    • Allow @SafeVargs on private instance methods.

    • Allow effectively final variables to be used as resources in the try-with-resources statement.

    • Allow the diamond with anonymous classes if the argument type of the inferred type is denotable.

    • Complete the removal, begun in Java SE 8, of the underscore from the set of legal identifier names. It can be still used by concatenating multiple underscores but should be avoided.

    • Added support for private interface methods. They need to be static. D
      * Java 10 allows package level methods without need to put them in any class.
      * Java 9 allowed Interfaces to have private and static method with implementation.
      * Java 8 already allows default method with implementation to support lambda functions.

    • Applet api is deprecated and SHA-3 cryptography hash is now supported.

    • Local variable type inference is added in the form of variable type var. It should be avoided to make it code more readable but it can come in handy in reducing boilerplate code in many standard api usage such as JDBC, HttpClient, JMS. However using var excessively could make your program difficult to understand. You should follow style guideline for local type inference.

    • Native-Header Generator Tool, javah is removed. Javac can be used with arguement -h and -jni to generate JNI headers.

    • JavaDoc now has a type ahead search box to search for modules, packages, classes, methods and other tags. Its not very sleek and fast but its better than having no search capability at all. Also the new JavaDoc is in HTML5, making it accessible.

    • JDK 9 has more compact string footprint in memory. Instead of 2 byte per char, it can be either one buy or two byte with an encoding flag bit indicating which storage is used. It will reduce string memory in most cases as well as substantial reductions of GC activity.

    • There is optional experimental AOT (Ahead of Time) compiler shipped with JDK 9. It scans the code and compiles indirect code path as well which could be lazily compiled when using JIT. It is not a production feature and should be used to only get familiar with the new concept.

    • JDK10 is Docker aware. It has new jvm options for docker and it can show container specific information such as cpu, memory usage based on docker setings instead of host os. To get more information on docker support please refer to docker blog.

    • Finally Java 11 is expected say final goodbuy to Corba, Java EE and JavaFx. Possibly Java swing will also handed over to open source community in future release.

    • ProcessHandle interface has utility methods to list processes, print its cpu time and other details.

    • The default KeyStore tis changed from JKS to PKCS12 (Public Key Cryptography Standards).