PRC2

Java Platform Module System

The Java Platform Module System has been introduced with Java 9 and had a long development history at the point of introduction. For the Java developers at Oracle and others involved in the OpenJDK project it was considered a necessary step to make Java fit for the future and make it possible to shed some old nasty problems. In the development of Java there have been only a few major release, although each release of Java introduces new features that are supposed to play nice with what is already available. This is called forward and backward compatibility, so old programs keep working but can be maintained by using new features.

The important major releases were:

  • Java 1. Duh.
  • Java 1.2 introducing the collection framework.
  • Java 5 introduces Generics and the first version of the concurrency framework. It also changed the naming scheme. Internally it still is Java 1.5.
  • Java 8 introduced a functional style of programming using Functional interfaces and lambda expressions
  • Java 9 introduced the Java Platform Module System, and the project under which it came into being was called JigSaw.
    That last name points to the idea of taking the JDK and run-time library apart and recompose it using a better partitioning, actually applying separation of concerns in a big way. The project has been a multi year undertaking.

The changes provide the following advantages for the Java platform developers (from project jigsaw)

  • Make it easier for developers to construct and maintain libraries and large applications;
  • Improve the security and maintainability of Java SE Platform Implementations in general, and the JDK in particular;
  • Enable improved application performance; and
  • Enable the Java SE Platform, and the JDK, to scale down for use in small computing devices and dense cloud deployments.

As always with Java, the intent is that the new Java release (9) should be able to work very much the same as the earlier versions, and it does, without problems. If you use the JVM and JDK in classpath mode, it works very much like older versions. You define the jar files you need for you app and off you go.

Although the intent always has been to make the transition as smooth as possible, when you want to use the module system to the fullest, it has some consequences.

  • Not everything in the JDK can be used in the same way as before, more protection is taking place as are checks.
  • A module is closed by default and must explicitly export packages, if they are needed from outside the module.
  • The classes in a module can’t be reflected on by default. If that is needed, the package must be made opened for reflection.
  • At the moment of writing these restrictions can be modified with startup flags of the JVM. Later Java releases may change the default settings, from rather permissive in Java 9 to more restricted in later releases. As an example, some settings became more strict with Java 16 and JEP 396 Strongly Encapsulate JDK Internals by Default.
Keynote session by Mark Reinhold 2015 introducing JigSaw
module graph
Figure 1. Java SE module graph

Having a modular project or a non-modular project depends on the presence of a small file with a well defined name in a specific place.

The module-info.java file should reside in the root folder of the java sources (which makes the defaul package). The name of the file is not a valid java type name and that is on purpose. The resulting class file contains the module declarations which specifies what a module needs (reads in JPMS terms) and what it provides (exports).

The keywords specific to the module declaration are

  • exports What the module provides. Use with one package per line.
  • module starts the declaration
  • open if before the module, opens the whole module for reflection. For instance for a module that defines all entities of a multi-module application.
  • opens allows reflection of a package. Use with one per package per line.
  • provides To indicate that a module implements some interface with one or more implementations.
  • requires Indicates what the module itself needs or reads. One package per line.
  • transitive Used with requires to indicate that there is a requirement that is then made readable to the users of this module as well.
  • to Used to restrict either exports or opens to specific other module(s).
  • uses To indicate that a module uses a service provided by another module.
  • with Used with provides to indicate which implementation(s) are available for the provided interface implementation. See the Service Loader API.

As a rule, the module java.base is 'required' by default, the same a java.lang is available or imported by default, considering packages to import.

Some examples of module-info.java files from the PRJ2-AIS-demo-project:

Assembler
module assembler_module{
    requires persistence_module;
    requires datarecords_module;
    requires businesslogic_module;
    requires gui_module;
}
DataRecords
module datarecords_module {
    exports datarecords;
}
Persistence
module persistence_module {
    requires datarecords_module;
    exports persistence;
}
BusinessLogic
module businesslogic_module {
    requires transitive datarecords_module;
    requires transitive persistence_module;

    exports businesslogic;
}
GUI
module gui_module {
    requires javafx.controls;
    requires javafx.fxml;
    requires java.logging;
    requires java.base;
    requires businesslogic_module;

    opens gui to javafx.fxml;

    exports gui;
}

And another example from our ALDA course in which a Service is provided:

ALDA sorting services
module sortingserviceapi {
    exports sortingservice; 1
}
// and
open module asortingservice {
    requires java.logging;
    requires sortingserviceapi;
    uses sortingservice.SortingServiceFactory; 2
    provides sortingservice.SortingServiceFactory
         with asortingservice.SortingServices; 3
}
  1. defines sortingservice.SortingServiceFactory in package sortingservice; this is an interface.
  2. uses and
  3. provides an inplementation of the SortingServiceFactory interface with a specific implementation: SortingServices.
Warning:

JPMS explicitely forbids:

  • Split packages, that is using the same package name in different modules with different content.
  • Cyclic dependencies as in A requires B requires A.

The dependencies are validated at the startup of the JVM, when you start Java. If any of the requirements is not met, the JVM refuses to start. This has the benefit that it is immediately obvious what is missing, instead of having a runtime error, when a class can’t be found, because the module has not been specified in the path, as is the case with the classpath mode.

The split package has been a bit of an issue for testing, because in a modern project, (business) source and tests are separated into separate source trees, often with the same package structure, which looks a bit like split packages. The build tools (e.g. maven) understand this and can organise the files such that the JVM accepts it anyway.

Changes in visibility

Java has always had the visibility modifiers public, protected, default, and private, in descending order of access. This is still effective with the same rules within a module. However the module boundaries add an extra line of visibility defense. A public element from module A is only visible to elements in module B if the public elements is part of an exported package. As far as reflection goes. An element in module A is accessible via reflection only if the package of the element is open (with the opens keyword) either globally or explicitly to module B ( e.g. module A { exports aPackage to B;}).

Java Modules and Testing

Encapsulation gone wrong: So much for shining security armor.

shiningArmor cropped Project Jigsaw, now known as the Java Platform Module System solves problems that lingered in the Java ecosystem since its inception. The standard encapsulation or visibility model with protection or visibility modes private, package private (default) provide too little defense against (ab)use of non-public API’s. That plus the fact that reflection is very powerful. A java security manager can put up some defense against reflection abuse, but enabling this kind of security is optional.[1] This made many internal details of the JDK free game. Much like a knight in shiny armor is defenseless against a can opener.


This is all fine and good, but testing, in particular Unit testing relies on access to the 'private' parts of a class and package, in particular the package private parts.

Black box vs White Box

In testing, one distinguishes between so called black box and white box tests.

Black box in this respect means that you cannot see the internals, but only those parts defined in the API. This is the way that a client package will use the API. Black box testing is easily supported by the module system, because the test classes behave as ordinary clients.
White box would better be called transparent box, but the name white box stuck. In white box tests you DO want access to the (package) private parts of your business code. This is a bit handicapped by the way the JPMS allows access.

Standard Source code organization.

source tree of genericdao
src
├── main
│   └── java
│       └── genericdao
│           ├── dao
│           ├── memory
│           └── pgdao
└── test
    └── java
        ├── genericdao
            ├── dao
            ├── memory
            └── pgdao

We are using maven, in which the unit test plugin is called surefire, the integration test plugin (which we did not use up to this point yet) is called failsafe.

The standard way source code is organized is by means of separate directories for the 'business' code and the test code in two separate directory trees inside the same project.
This practice, which prevents test code to land in the final application or library jar file is useful, so we will keep it as it is.

However, JPMS does not allow so called split packages. Surefire addresses this problem by telling the JVM to patch the module we are testing with the classes defined in the test source tree. It is as if the test classes are put in the business source tree.
This allows the test classes access to the package private parts of the classes, the way it worked in before JPMS.

Module definition of sebi dao.
module nl.fontys.sebidao {
    exports nl.fontys.sebivenlo.dao;
    exports nl.fontys.sebivenlo.dao.memory;
    exports nl.fontys.sebivenlo.dao.pg;
    requires java.logging;
    requires java.naming;
    requires java.sql;
    requires org.postgresql.jdbc;
}

The module definition above exports three packages and declares itself dependent on 3 modules from Java API and one postgresql module.

Unit tests, and in particular the testing libraries such as AssertJ, and Mockito use reflection to do their work. The simplest example is JUnit itself, which uses reflection to read the annotations in the class file. So does Mockito. AssertJ uses reflection to get to fields (for deepEquals) and methods. Reflection is a tool very sharp indeed, to get to the guts of the System Under Test. Of these Test libraries JUnit and AssertJ have module definitions already. Mockito and postgresql do not have that at the time of writing this document (April 2020).

The quickest way to allow the testing tools access is to fully OPEN the packages of the SUT to world. Because this happens during testing, surefire is instructed to tell the jvm (java), that runs the test, to open those packages to the world. The snippet from the maven pom file that realizes that is given below. You can copy and adapt it for your own purposes.

sebipom project with surefire.opens property to open op required packages for testing. Requires sebipom >= 3.0.2
<properties>
  <surefire.opens> 1
    --add-opens nl.fontys.sebivenlo.genericdao/genericdao.dao=ALL-UNNAMED 2
    --add-opens nl.fontys.sebivenlo.genericdao/genericdao.memory=ALL-UNNAMED
    --add-opens nl.fontys.sebivenlo.genericdao/genericdao.pgdao=ALL-UNNAMED
    --add-opens nl.fontys.sebivenlo.genericdao/usertypes=ALL-UNNAMED
    --add-opens nl.fontys.sebivenlo.genericdao/entities=ALL-UNNAMED,genericmapper
  </surefire.opens>
  3
</properties>
  1. We are adding a property for the surefire plugin which is picked up by sebipom.
  2. In particular the arguments that are passed to the JVM that runs the tests, which appends --add-opens commands for all packages that require unit tests.
  3. Other properties are left out for brevity.

Of particular importance, and project specific are the --add-opens commands, which you must adapt to your own project’s pom file with your own module and package names.

opens
Figure 2. opens sets module and package

Opens is the most powerful way of exporting a package. It allows full access (as in exports) plus reflection. We could try to make more specific export lines such as --add-export, but although more precise, that will not help very much, because the access enabling lines will only take effect during the (surefire) unit tests.
If you have failsafe integration tests, you will have to do the same for the failsafe plugin, although failsafe tests should stick to black box testing, where such can opener style configuration should not be required.

Lambda and Streams

We will start with a short recap and some refinements on λ-expressions, followed by application in streams.

Functional Interface Rationale

  • Java: backwards compatible: do not break existing code with new features.
  • Way forward: enhancements to the collection framework.
    • Extending interfaces without breaking implementations
      • add default functions
      • add static helper functions

Example: compare java 7 and java 8 Comparator:

  • The Java 7 version has two abstract methods, which were the only kind in Java before 8.
  • The Java 8 version has one abstract method, making it into a functional interface,
    7 default methods and
    8 static methods whose functionality is now available to every class implementing Comparator.

Have a look yourselves: Java 7 Comparator and Java 8 Comparator

The concept of functional interfaces or single abstract method is the secret ingredient to let the compiler guess the appropriate shape of the function that matches a lambda expression.

<S.H.A.P.E.> of java.util.functions

The java.util.functions package, which was introduced in Java 8, contains only functional interfaces and nothing else. The package contains the most common shapes of functions that may occur in lambda expressions. You can see that all reference type functional interfaces heavily rely on generics. The use of the proper generics definition is essential for functional interfaces to function properly.

We will use the notation <input type> → <result type> for the shape of a Functional interface. Note that the shape is determined by the one (abstract) method of the functional interface. The style used in java.util.function is somewhat mnemonic in choosing the generic type parameters in the type interface type definition.

  • T as only parameter, either a input or output e.g.
    • Consumer<T>: shape <T> → {} black hole? Eats Ts.
    • Supplier<T> : shape () → <T> , source of Ts.
  • Function<T,R>: shape <T> → <R> also known as mapping function, from one type to another.
  • BiFunction<T,U,R>: shape <T,U> → <R>.

There are also a few specialized interfaces that deal with the primitive types int, long and double. Their rationale is performance and memory footprint, in short: efficiency. Dealing with e.g. arrays of primitive types is much more efficient than using the associated wrapper types, int vs Integer. You save the object allocation memory-wise and the dereferencing and unwrapping execution time-wise.

  • ToDoubleBiFunction<T,U>: shape <T,U> → double producing a double from two arguments.
  • LongConsumer: shape long →() munges longs to make them disappear. :-)

The package documentation makes an interesting read.

Exceptions in Lambda expressions need special consideration. In particular checked exceptions actually counteract the conciseness of lambda expression. In those cases, where the lambda expression or reference method throws an exception, that exceptions is commonly wrapped in an unchecked exception, e.g. UncheckedIOException to wrap an IOException. UncheckedIOException has been introduced in Java 8 for just that purpose.

You can also roll your own.
If you need to declare a functional interface that potentially throws an exception, the following is a way to do that:

throwing functional interface
interface ThrowingBiFunction<T,U, R,<X extends Throwable>> {
    R apply(T t, U u) throws X;
}

Shape wise it looks like <T,U> → R | X pronounced as from T and U to either, R the result, or X, the thrown exception.

Where is toString?

You might miss toString() on lambda expressions, and you would be right: Lambda expressions lack a useful toString. What you get by calling toString() on a lambda expression is something rather useless, concocted by the JVM.

toString() is useless with lambda expressions
Comparator<Student> gradeComp =
      (a,b) -> Double.compare(a.getGrade(),b.getGrade());

When you invoke toString() in say jshell, you get.

jshell> class Student{ double grade; double getGrade(){return grade;}}
|  created class Student

jshell> Comparator<Student> gradeComp = (a,b)
    -> Double.compare(a.getGrade(),b.getGrade());
gradeComp ==> $Lambda$16/0x00000008000b3040@1e88b3c

jshell> gradeComp
jshell> gradeComp.toString()
$3 ==> "$Lambda$16/0x00000008000b3040@1e88b3c"

Method References

Since the shape of a functional interface has everything a method definition has, bar its name, it should be no surprise that this can be used to advantage. Everywhere where a functional interface is allowed you can also provide a so called Method reference. Quite often, but not always this can be a shorthand and thereby improving readability.

Method references have two parts, separated by two colons: ::.

  • The left hand side of the :: defines the method context, either instance of class (for method references to static methods)
  • The right hand side is the method name. The compiler will do the matching in case of overloaded methods, but sometimes it may give up and will let you know by using the word ambiguous in its complaint.

There are four kinds of references:

Table 1. Source Orace tuturial.
KindExample
Reference to static methodContainingClass::staticMethodName
Reference to an instance method of a particular objectcontainingObject::instanceMethodName
Reference to an instance method of an arbitrary object of a particular typeContainingType::methodName
Reference to a constructorClassName::new
Reference to a method of this instancethis::instanceMethodName

Streams, SQL like operations expressed in Java 8

Here too, the package documentation is an interesting read.

A stream can be considered a conveyor belt on which objects experience operations. The belt starts at a source (for instance a Supplier) and can on or more intermediate operations and end with a Terminal operation.

streamops

from Lucas Jellema of amis blogs

Here your see a physical stream and a collect operation.

Conveyor and collecting

SQL like functions

assume you want to know the sum of the wages for women.
select sum(salary)
from   people
where  gender = 'FEMALE'
same in Java, get the sum of payments to women.
    int womensPay =
        people.stream()                                         1
            .filter(p -> p.getGender() == Person.Gender.FEMALE) 2
            .mapToInt(p -> p.getSalary())                       3
            .sum();                                             4
  1. use people collection as source and start stream
  2. where …​ where gender = female
  3. mapping is often retrieving the value of a member, get salary, note that it is a Map ToIntFunction<Person> specialised function
  4. aggregation, like sum() here is a terminal operation
variant using a method reference.
    int womensPay =
        people.stream()
            .filter(p -> p.getGender() == Person.Gender.FEMALE)
            .mapToInt( Person::getSalary )                      1
            .sum();
  1. This alternative uses a method reference, which can be substituted for a lambda expression when their shapes match.

Collection Enhancements and general tips

It never hurts to browse through an API once in a while. You might get new ideas or find a cool feature.

Improved Map in Java 8

I personally find the java.util.Map quite interesting because it got a number of very cool features in Java 7 and 8 that are really very helpful if you want to implement things like caches. If you are into caching and expect many entries, consider a WeakHashMap, that also helps to solve the problem of keeping the cache small and auto-evacuate when entries are no longer used.

In order of appearance in the api doc:

  • compute Attempts to compute a mapping for the specified key and its current mapped value (or null if there is no current mapping).
  • computeIfAbsent If the specified key is not already associated with a value (or is mapped to null), attempts to compute its value using the given mapping function and enters it into this map unless null.
  • computeIfPresent If the value for the specified key is present and non-null, attempts to compute a new mapping given the key and its current mapped value.
  • getOrDefault Returns the value to which the specified key is mapped, or defaultValue if this map contains no mapping for the key.
  • putIfAbsent If the specified key is not already associated with a value (or is mapped to null) associates it with the given value and returns null, else returns the current value. and of course
  • forEach Performs the given action for each entry in this map until all entries have been processed or the action throws an exception.
which allows you to quickly look at the contents of a map with a one-liner
    map.forEach((k, v) -> System.out.println(k + "=" + v));

The fun thing is that all these methods are default methods, so they apply to all classes that implement the java.util.Map interface, including the ones you roll yourselves, might you feel the inclination. To implement a special map that has specific extra properties, you only need to extend AbstractMap which needs the implementation of one or two methods only, depending if you want an unmodifiable of modifiable one.

Optional demystified

Many terminal stream operations return an Optional. Rationale: the required element may not be present, or the Stream is empty to start with. Or the filter throws everything out.

Optional is an interesting concept all by itself.

  • An Optional can be considered as a very short stream of at most one element.
  • You can apply a map operation to an Optional, which transforms the content of the optional (if any) and produces an optional containing the result of the mapping.
  • You can even stream() the optional, which allows you to apply all operation that a stream provides (since 9).

All this can be used in an algorithm, in which you want to chain a bunch of methods, each of which requiring that the parameter is non-null.

Traditional returning null
  SomeType result;
  var a = m1();
  var b=null;
  var c=null;
  if ( null != a ) {
    b = m2( a );
  }
  if ( null != b ) {
    c = m3( b );
  }

  if ( null != c ) {
    result = m4( c );
  }
  return result;
  // do something with result
Using Optional. Returning Optional<SomeType>
  Optional<SomeType> result
     = m1ReturningOptional() 1
      .map( a-> m2( a ) )
      .map( b-> m3( b ) )
      .map( c-> m4( c ) );
  return result;
  // do something with Optional result
  1. The chain starts with a method returning an optional. The remaining methods m2(…​), m3(…​), and m4(…​) are unchanged.

In the last example, an Optional<SomeType> is returned, that can either be unwrapped of passed on as such for further processing.

Reading

JPMS

Lambda and streams

  • Horstmann Core Java, Ed 11, Vol I CH 6.2 Lambda Expressions
  • Horstmann Core Java, Ed 11, Vol I CH 9 Collections
  • Horstmann Core Java, Ed 11, Vol II CH 1 Streams

  1. It was enforced in the now deprecated web applets