Brian Goetz Views on Functional Programming and the Future of Java

Brian Goetz was invited to speak to the Dutch Java community in a meetup, organized by Code Nomads, OpenValue and JPoint. If you are a Java developer then you would most likely have heard of Brian. Or at least used his improvements to the Java language in JDK 8 with Lambda expressions. Brian was the specification lead for JSR-335 (Lambda Expressions for the Java Programming Language). He has also written the best selling book, “Java Concurrency in Practice” and over 70 articles on Java development. Since Jimmy Carter was president he has been fascinated about programming.

Brian gave the following two talks:

FP is Dead: Long Live FP

“While programmers tend to divide themselves into communities along language or paradigm lines, the reality is that the OO and FP communities have a great deal to learn from each other. As developers, we should learn classic OO, learn classical FP, and then strive to rise above them both.“

A more philosophical talk about what it is really to be a software developer. Do we choose the language or tool to fit the problem or do we try and fit the problem into our favourite tool? There are developers who only want to write code in Go or Haskell or Scala or Erlang or Java. Brian gave convincing arguments that Java allows you to have the best of both worlds, functional behaviour when you need it and object orientated paradigm as well.

Java Language Futures: 2018 edition

“The accelerated release cadence for the Java platform offers us many more opportunities for delivering new features — and we plan to do just that. Join Brian on a whirlwind tour of the language features under development in Project Amber.“

JDK 10

In line with the new release cadence of JDK there was JDK 10 delivered on time in March 2018 which included the following JDK Enhancement Proposals (JEPs):

The above enhancements are mostly internal however the new var local variable type inference can be shown. The general idea is to reduce the amount of verbosity in writing Java, increasing developer productivity and maybe even less errors. There are also a list of deprecated APIs for JDK 10 which we will be removed in JDK 12. An example from the classic [Reading from and Writing to a URLConnection] of var local variable type inference for the reading part is shown below.

import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.net.URL;

public class URLConnectionReader {
    public static void main(String[] args) throws Exception {
        var oracle = new URL("https://www.codenomads.nl/");
        var yc = oracle.openConnection();
        var in = new BufferedReader(new InputStreamReader(yc.getInputStream()));
        String inputLine;
        while ((inputLine = in.readLine()) != null)
            System.out.println(inputLine);
        in.close();
    }
} 

JDK 11

So what’s coming next in JDK 11? Here are the new language features that are under development with Project Amber:

To be able to experiment with JDK 11 there are early access builds but they do not have the branches for the above features merged in. The other option is to build from the OpenJDK JDK 11 branches. For example for the JEP-326 Raw String Literals branch you need to do the following:

hg clone http://hg.openjdk.java.net/amber/amber
cd amber
hg update patterns
sh configure && make images
./build/*/images/jdk/bin/java -version"
hg clone http://hg.openjdk.java.net/amber/amber
cd amber
hg update patterns
sh configure && make images
./build/*/images/jdk/bin/java -version"

In the Code Nomads GitLab repository in the following snippet listed below shows how to create a Docker image of a locally built OpenJDK Project Amber branch.

FROM centos:centos7
MAINTAINER Oliver Carr <oli@ozoli.io>

ENV LANG C.UTF-8

WORKDIR /opt

ARG JDK_DIR=/amber/jdk
COPY $JDK_DIR /opt/jdk-11

ENV JAVA_HOME /opt/jdk-11
ENV PATH="${JAVA_HOME}/bin:${PATH}"

RUN useradd -ms /bin/bash javauser

ARG MAVEN_VERSION=3.5.3
ARG USER_HOME_DIR="/home/javauser"
ARG SHA=b52956373fab1dd4277926507ab189fb797b3bc51a2a267a193c931fffad8408
ARG BASE_URL=https://apache.osuosl.org/maven/maven-3/${MAVEN_VERSION}/binaries

# Maven fails with 'Can't read cryptographic policy directory: unlimited'
# because it looks for $JAVA_HOME/conf/security/policy/unlimited but it is in
# /etc/java-9-openjdk/security/policy/unlimited
#RUN ln -s /etc/java-10-openjdk /usr/lib/jvm/java-10-openjdk-$(dpkg --print-architecture)/conf

RUN mkdir -p /usr/share/maven /usr/share/maven/ref \
  && curl -fsSL -o /tmp/apache-maven.tar.gz ${BASE_URL}/apache-maven-${MAVEN_VERSION}-bin.tar.gz \
  && echo "${SHA}  /tmp/apache-maven.tar.gz" | sha256sum -c - \
  && tar -xzf /tmp/apache-maven.tar.gz -C /usr/share/maven --strip-components=1 \
  && rm -f /tmp/apache-maven.tar.gz \
  && ln -s /usr/share/maven/bin/mvn /usr/bin/mvn

ENV MAVEN_HOME /usr/share/maven
ENV MAVEN_CONFIG "$USER_HOME_DIR/.m2"

COPY mvn-entrypoint.sh /usr/local/bin/mvn-entrypoint.sh
COPY settings-docker.xml /usr/share/maven/ref/

WORKDIR /home/javauser
USER javauser

ENTRYPOINT ["/usr/local/bin/mvn-entrypoint.sh"]
CMD ["mvn", "--version"]

The Docker images for the JEP-305 Pattern Matching and the JEP-326 Raw String Literals Project Amber branches are available on the Code Nomads DockerHub. Note these Docker images are not for production use.

So lets show an example for these two features, plus one more for fun!

JEP-305 Pattern Matching

For our code example we will use a simple Maven project. To build the project we will use the Docker image for the OpenJDK pattern matching branch we build earlier. The entire source code is available here and the new pattern matching for case statements is shown below:

 public static String toString(final Object object) {
        switch (object) {
            case null: throw new AssertionError("Null object given");
            case Character character : return String.format("character: %c", character);
            case Integer integer : return String.format("Integer: %d", integer);
            case String string : return String.format("String: %s", string);
            default: throw new AssertionError("Object not handled");
        }
    }

The pattern matching branch also includes some of the JEP-302 Lambda Leftovers implementation. The code below shows how it is now possible to use the new var keyword introduced in JDK 10 in Lambda parameters. The comments are removed for to use less lines.

public class Complex {
    final private Double x, y;

    public Complex(final Double x, final Double y) {
        this.x = x;
        this.y = y;
    }

    public Double getX() {
        return x;
    }

    public Double getY() {
        return y;
    }
}
import java.util.function.DoubleBinaryOperator;

public class ComplexUtils {
    private static DoubleBinaryOperator addFunction = (var x, var y) -> x + y;

    public static Double addParts(final Complex complex) {
        return addFunction.applyAsDouble(complex.getX(), complex.getY());
    }

    public static String toString(final Complex complex) {
       return String.format("Complex: real: %.2f imaginary: %.2f", complex.getX(), complex.getY());
    }
}

Note the var keyword usage in the addFunction above.

JEP-326 Raw String Literals

The JEP-326 Raw String Literals aims to make working with String easier for developers. Most other languages provide this support that reduces the amounts of escaping required and thus reducing errors and improving readability. The source code for this example is here.

   public static String sqlSnippet() {
        final var sql =
        ```select *
 from USERS

        where id=1```;
        return sql;
    }

    public static String htmlSnippet() {
        return `<html>
                   <body>
                       <p>Hello World.</p>
                   </body>
               </html>`;
    }

Above you can see the use of a triple back quotes for the SQL example an single back ticks in the HTML example.

Unit Testing with JDK 11

To unit test the above sample methods it was not possible to use the standard Maven test phase because the new JDK 11 version was not supported. So to create a JUnit test suite the following was added for the Raw String Literals project. First the JUnit TestSuite.

@RunWith(Suite.class)
@Suite.SuiteClasses({
        ComplexUtilsTest.class,
        ObjectUtilsTest.class
})
public class TestSuite {
}

Second a TestSuiteRunner with a main class to invoke from the GitLab CI/CD Pipeline.

public class TestSuiteRunner {
    public static void main(final String[] args) {
        final Result result = JUnitCore.runClasses(TestSuite.class);
        System.out.printf("Test ran: %s, Failed: %s%n", result.getRunCount(), result.getFailureCount());
        result.getFailures().forEach(failure -> {
            System.out.printf("%s %s", failure.getDescription(), failure.getMessage());
        });
        if (result.wasSuccessful()) {
            System.out.println("All tests finished successfully...");
        }
    }
}

This TestSuiteRunner was invoked using the following command in the .gitlab-ci.yml file shown below.

image: codenomads/openjdk-project-amber-raw-string-literals:latest

variables:
  MAVEN_OPTS: "-Djava.awt.headless=true -Dmaven.repo.local=./.m2/repository"
  MAVEN_CLI_OPTS: "--batch-mode --errors --fail-at-end --show-version"

cache:
  paths:
    - ./.m2/repository
  # keep cache across branch
  key: "$CI_BUILD_REF_NAME"

stages:
- dependencies
- build
- test

dependencies_job:
  stage: dependencies
  script:
    - "mvn dependency:get -Dartifact=org.hamcrest:hamcrest-core:1.3 $MAVEN_CLI_OPTS"
    - "mvn dependency:get -Dartifact=junit:junit:4.12 $MAVEN_CLI_OPTS"
    - "mvn dependency:get -Dartifact=org.apache.commons:commons-lang3:3.7 $MAVEN_CLI_OPTS"

build_job:
  stage: build
  dependencies:
    - dependencies_job
  script:
    - "mvn clean compile $MAVEN_CLI_OPTS"
    - "mvn test-compile $MAVEN_CLI_OPTS"
  artifacts:
    paths:
      - target/

test_job:
  stage: test
  dependencies:
    - build_job
  script:
    - $JAVA_HOME/bin/java -cp ${CI_PROJECT_DIR}/target/test-classes:./.m2/repository/junit/junit/4.12/junit-4.12.jar:./.m2/repository/org/hamcrest/hamcrest-core/1.3/hamcrest-core-1.3.jar nl.codenomads.TestSuiteRunner
  artifacts:
    paths:
      - target/

Above you will see how dependency management is quite a manual process and the script to run the TestSuiteRunner with the correct class path.

Concluding Remarks

We have shown the new enhancements to the Java language with code samples and Docker images open sourced for you to play with. Note again not for production use! Any issues reach out to use by creating an issue in the GitLab projects or via email or Twitter.

The last and best question of the evening was: “So are you just copying all the nice language features from Scala?” Brian kindly has if he could take the question to task so to speak.

Lets start from the end. Scala. Scala was not the first language to invent pattern matching or some other cool feature you like. Programming languages evolve from other programming languages taking the lessons learnt from others and adding new syntax or concepts. So yes Java is adding features that you have already been using in Scala but not just copying them for the sake of it.

Just. Just copying a language feature sounds easy on paper. But it reality it is very difficult. You can follow all the discussions about these new features on our email lists (here and here). Or mentioned previously JEP 301 Enhanced Enums is more delayed until JDK 12. So although the “JDK engineers write in C++ so you dont have to” their task is not as simple as it looks.

And these features are not easy to implement in any language. Great care is been taken to add them in the best way possible for the Java ecosystem.

This article is written by Oliver Carr (Lead Developer @ Code Nomads). Oliver has been playing around computers since an early age and has lived in the Netherlands for almost 12 years. From developing Fortran code for a CM-5 super computer to integrating speech recognition software to more recently developing e-commerice platforms at TomTom, AHold, Rabobank and VEON. Oliver has developed software in C++, Java, Scala and Python and enjoys learning new techniques and coaching less experienced developers to the next level.