Java 9: na de GA

Versie 9 is live! Donderdag 21 september 2017 was de General Availability – einde werkdag in San Francisco, 7 uur later dan CEST; aan deze kant van de oceaan pas op vrijdag. Het was het wachten waard! Al moet gezegd dat deze versie al anderhalve maand beschikbaar was: early access build 181 heeft geen nieuwe issues meer opgeleverd. Fun fact: in de OpenJDK issue tracker voor Java 9 staan meer dan 20.000 opgeloste issues!

Dit is het tweede en laatste artikel in deze reeks over de Java 9 release. Uit de 91 Java Enhancement Proposals (JEPs) worden er enkele uitgelicht. In het vorige nummer kwamen aan bod: JShell, Milling Project Coin, Multi-Release JARs en Factory methods voor Collections.

We pikken de draad weer op bij de Process API, CompletableFuture, Project Verona, Security, Performance, Flow reactive streams en @Deprecated. Ook het project Milling Project Coin wordt nog even aangestipt.

Hedzer Westra

 

Vervolg ‘Milling Project Coin’

Nummer vier van de vijf verbeterpunten is @SafeVarargs voor private methods. Deze annotatie signaleert de compiler dat de combinatie varargs & generics ‘veilig’ is bevonden door de programmeur. In Java 7 was dit alleen toegestaan op static en final methods. Zie codevoorbeeld 1.

 

@SafeVarargs

private void safeVarargsOnPrivateInstanceMethods(List<String>… stringLists) {

    for (List<String> stringList : stringLists) {

        System.out.println(“list: ” + stringList);

    }

}

codevoorbeeld 1: @SafeVarargs vanaf Java 9 toegestaan op private methods.

Als laatste: private (static) methods in interfaces. Dit is een uitbreiding van de Java 8 default methods. Hiermee wordt een betere encapsulatie & factorisatie van default interface methods mogelijk. Zie codevoorbeeld 2.

 

interface NameAndCountry {

    static String getName() { return “Jan Modaal”; }

    default String getCountry() { return defaultCountry(); }

    private String defaultCountry() { return “Nederland”; }

}

codevoorbeeld 2: Java 9 private methods in interfaces.

 

CompletableFuture

CompletableFuture is voorzien van support voor delays, timeouts en subclassing. Zie codevoorbeeld 3. Ook zijn er enkele Factory methods toegevoegd voor CompletableFuture en zijn kleinere broertje CompletionStage: completedStage(value), failedStage(Throwable) en failedFuture(Throwable). Dit in aanvulling op de al bestaande completedFuture(value). Hiermee zijn instanties aan te maken die direct compleet dan wel gefaald zijn.

 

class MyCompletableFuture<T> extends CompletableFuture<T> {

    @Override

    public <U> CompletableFuture<U> newIncompleteFuture() {

        return new MyCompletableFuture<>();

    }

}

CompletableFuture<Object> future = new MyCompletableFuture<>()

    .completeAsync(() -> “resolved asynchronously”, CompletableFuture.delayedExecutor(10, TimeUnit.SECONDS))

    .orTimeout(4, TimeUnit.SECONDS)

    .completeOnTimeout(“alternative result”, 3, TimeUnit.SECONDS);

Thread.sleep(5_000L);

System.out.println(“result: ” + future.get());

codevoorbeeld 3: Diverse van de nieuwe CompletableFuture methodes in één voorbeeld.

 

Verona: New Version-String Scheme

Een prettige aanpassing voor toolbouwers is de volgende. Het formaat van de versie-string voldoet vanaf nu altijd aan

$MAJOR(.$MINOR.$SECURITY)?(+$PATCH)?

Voorheen werden met name minor updates, buildnummers & security patches onduidelijk weergegeven. Dit leidde tot vele waarden voor één release, bijvoorbeeld: 1.8, 1.8.0, 1.8.0_25, 1.8.0_25-b18 en 8u25. Dit is lastig te herkennen voor tools zoals Maven. Die situatie is nu verholpen – natuurlijk na het updaten van alle plugins die alleen het oude formaat ondersteunen.

We zijn hiermee verlost van de ‘1.’ prefix die – nadat Java2 werd opgevolgd door Java 1.3 – voor altijd aan Java leek te blijven kleven.

Zie tabel 1 en figuur 1.

 

System property Java 8 Java 9
java.version 1.8.0_25 9
java.runtime.version 1.8.0_25-b18 9+181
java.vm.version 25.25-b02 9+181
java.specification.version 1.8 9
java.vm.specification.version 1.8 9

Tabel 1: De waarden van vijf System properties voor een Java 8 en de Java 9 release

java version “1.8.0_25”

Java(TM) SE Runtime Environment (build 1.8.0_25-b18)

Java HotSpot(TM) 64-Bit Server VM (build 25.25-b02, mixed mode)

java version “9”

Java(TM) SE Runtime Environment (build 9+181)

Java HotSpot(TM) 64-Bit Server VM (build 9+181, mixed mode)

Figuur 1: De uitvoer van `java -version` voor een Java 8 en de Java 9 release.

 

Security

Op het gebied van security voegt Java 9 het volgende toe:

  • Datagram Transport Layer Security (DTLS). Dit protocol maakt pakket-geörienteerde secure connecties mogelijk. Je zou het je kunnen voorstellen als UDP over TLS. Beide momenteel bestaande protocolversies – 1.0 en 1.2 – worden ondersteund.
  • Standaard PKCS12 Keystores. De bekende tool keytool.exe maakte tot nog toe standaard keystores aan van type JKS (Java Key Store), waarbij extensie .jks vaak gebruikt werd. Dit wordt nu de veel gangbaarder industriestandaard PKCS #12 (van RSA labs). Gangbare bestandsextensie is .p12. Opvallend is overigens dat de JDK-meegeleverde truststore cacerts nog steeds in het JKS formaat opgeleverd wordt.
  • Ondersteuning voor het door NIST gestandaardiseerde algoritme Deterministic Random Bit Generator (DRBG) is toegevoegd, naast de bestaande SecureRandom algoritmes SHA1PRNG en Windows-PRNG. Het is het nieuwe standaard algoritme bij aanroepen van new java.security.SecureRandom().
  • Het SHA-3 hashing algoritme. Dit algoritme werd door NIST gepubliceerd in augustus 2015. Het is nog niet de bedoeling dat dit SHA-2 gaat vervangen. Er zijn nog geen significante aanvallen op SHA-2 bekend. Wel wil de NIST toekomstvast zijn door dit algoritme toe te voegen aan de bestaande lijst van gestandaardiseerde hash algoritmes. Berekende hash waarden kunnen 224, 256, 385 of 512 bits groot zijn.
  • Afkeuren van SHA-1 certificaten. Op SHA-1 zijn vanaf 2005 cryptografische aanvallen ontdekt. Sinds 2010 wordt aangeraden om naar SHA-2 (of SHA-3) over te stappen.
  • TLS Application-Layer Protocol Negotiation (ALPN). Dit is een toevoeging aan het TLS handshake protocol voor HTTP/2. Hiermee is één request-response stap minder nodig bij het opzetten van een nieuwe verbinding.
  • Online Certificate Status Protocol (OCSP) Stapling voor TLS. Hiermee wordt het mogelijk om effiënt de geldigheid van een TLS X.509 certificaat te controleren. Dit is geïmplementeerd zoals beschreven in RFC 6066 en 6961.

Performance

De performance is op diverse vlakken verbeterd door middel van diverse compiler, JVM & GC aanpassingen. Ook op het gebied van concurrency en security zijn stappen gezet in performanceverbetering. Enkele voorbeelden zijn: Smart Java Compilatie, GHASH & RSA CPU instructies, compact & interned Strings, Ahead-of-Time Compilatie, Improved Contended Locking en Spin-Wait Hints.

Zie ook de referentie over Java 9 performance.

 

Process API

java.lang.Process bestaat al sinds Java 1.0. Hiermee is het mogelijk om nieuwe processen te starten op het onderliggende OS. Met Java 9 zijn er zeven nieuwe methods toegevoegd. Hiermee wordt het eindelijk mogelijk om informatie over processen op te vragen, ze af te breken en te monitoren. Voorbeeld: welke processen draaien er, wat is het huidige (JVM) proces, en wat is de process id (PID) van een proces. Ook de gebruikte command line, user name en procestijden worden inzichtelijk.

Starten, monitoren en stoppen van externe processen is inherent asynchroon. De API maakt daarom uitgebreid gebruik van Optional, CompletableFuture en Stream – classes die asynchrone operaties ondersteunen. Je ziet dit terug in de twee nieuwe interfaces ProcessHandle en ProcessHandle.Info.

Zie Codevoorbeeld 4.

 

 

Duration jvmDuration = ProcessHandle.current().info().totalCpuDuration().orElse(Duration.ZERO);

long firstPid = ProcessHandle.allProcesses().findFirst().get().pid();

String userOf9792 = ProcessHandle.of(9792).get().info().user().get();

String command = Runtime.getRuntime().exec(“/bin/sh”).toHandle().info().command().get();

someHandle.onExit().thenRun(() -> System.out.println(“klaar”)).get(1, TimeUnit.SECONDS);

Codevoorbeeld 4: ProcessHandle toegepast

 

Reactive Streams: Flow

Met Flow is Java 9 ook in het ‘Reactive Streams’-bootje gestapt. Hiermee komt push-based stream processing ook binnen de JDK beschikbaar. Aangezien de JDK niet afhankelijk kan zijn van org.reactivestreams:reactive-streams:1.0.1 is er een 1-op-1 identieke definitie opgenomen in de JDK. De verwachting is dat de bestaande Reactive Streams implementaties binnen korte tijd migreren naar de JDK Flow interfaces.

In class java.util.concurrent.Flow zijn vier inner interfaces opgenomen met in totaal zeven methoden. Deze zijn allemaal gedefinieerd als ‘one-way’ (void) berichten.

Een Publisher publiceert berichten naar aangemelde Subscribers. De Subscriber ontvangt berichten volgens het volgende protocol (in BNF notatie): onSubscribe onNext* (onError | onComplete)?. Een Processor is zowel Subscriber als Publisher. Deze transformeert een datastroom.

De Subscription omvat de connectie tussen Publisher en Subscriber. Een connectie kan verbroken worden middels cancel(). Ook kan ‘back pressure’ (ook wel ‘flow control’) worden toegepast door de request(numItems) methode. Back pressure is hét belangrijke onderdeel van Reactive Streams: hiermee kan de ontvanger van de push-based data aangeven hoeveel capaciteit er beschikbaar is voor dataverwerking.

De JDK levert slechts één public implementatie van deze vier interfaces: de SubmissionPublisher.

Zie codevoorbeeld 5.

 

 

static class MySubscriber<T> implements Flow.Subscriber<T> {

    private Flow.Subscription subscription;

    @Override public void onSubscribe(Flow.Subscription subscription) {

        System.out.println(“Subscription: ” + subscription);

        this.subscription = subscription;

        subscription.request(1);

    }

    @Override public void onNext(T item) {

        System.out.println(“Item: ” + item);

        subscription.request(1);

    }

    @Override public void onError(Throwable throwable) {

        System.out.println(“Error: ” + throwable);

    }

    @Override public void onComplete() {

        System.out.println(“Complete”);

    }

}

SubmissionPublisher<String> publisher = new SubmissionPublisher<>();

Flow.Subscriber<String> subscriber = new MySubscriber<>();

publisher.subscribe(subscriber);

List<String> messages = List.of(“a”, “couple”, “of”, “words”);

messages.forEach(publisher::submit);

Thread.sleep(1_000L);

publisher.close();

codevoorbeeld 5: Flow codevoorbeeld.

 

@Deprecated

De @Deprecated annotatie is uitgebreid met twee attributen: since en forRemoval. Die laatste is optioneel, met als standaardwaarde false.

De betekenis van since moge duidelijk zijn: hiermee geef je aan sinds welke versie de desbetreffende code verouderd is.

Met forRemoval wordt aangegeven of de verouderde code binnen afzienbare tijd verwijderd gaat worden. In geval van de JDK zelf zal dat de eerstkomende release zijn. Om je daar extra alert op te maken, is de bekende @SuppressWarnings(“deprecation”) vanaf Java 9 niet voldoende om in code die forRemoval=true APIs aanroept, compiler warnings te onderdrukken. Daarvoor is de nieuwe vlag @SuppressWarnings(“removal”) vereist. Zie codevoorbeeld 6.

Niet-gewijzigde code kan bij migratie naar Java 9 dus opeens extra warnings krijgen. Bijvoorbeeld als je gebruik maakt van System.runFinalizersOnExit() of Thread.destroy(). In de JDK 9 javadoc vind je een lijst van zo’n vijftig ‘deprecated for removal’ velden, methodes, classes, tools en modules. Modules? Jazeker, er zijn zeven modules die al meteen bij de introductie van jigsaw deprecated zijn.

Het gaat dan om java.activation, java.corba, java.transaction (alledrie gerelateerd aan CORBA), java.xml.bind (Rob Scholte merkte in editie 4-2017 al op dat je hiervoor dient te migreren naar Maven dependency javax.xml.bind:jaxb-api), java.xml.ws en java.xml.ws.annotation (JAX-WS vervalt). De laatste is java.se.ee, een ‘lege huls’ die bovenstaande zes deprecated modules omvat, maar zelf geen code bevat.

Enkele noemenswaardige JDK onderdelen die per Java 9 deprecated zijn geworden: Constructors van de primitive wrappers (Integer, Boolean, et cetera). Gebruik in plaats van new Integer(1) of new Integer(“1”) liever de static methodes valueOf() en parseInt().

In plaats van de java.util.Observer en Observable classes wordt voorgesteld om java.beans, java.util.concurrent of Flow te gebruiken.

Daarnaast is Object.finalize() eindelijk deprecated. Al sinds jaar en dag wordt ons geleerd niet op deze methode te vertrouwen, aangezien deze niet te gebruiken is waarvoor deze van origine bedoeld is: het tijdig opruimen van schaarse of gedeelde system resources. Object.finalize() was bedoeld om de overgang vanuit C++ makkelijker te maken, maar is in combinatie met het in die omgeving niet bestaande Garbage Collection een slechte match.

 

 

@Deprecated(since = “2.0”, forRemoval = true)

public void someAncientMethod() {

System.out.println(“Doing ancient stuff…”);

}

 

@SuppressWarnings({“deprecation”, “removal”})

public void someCode() {

someAncientMethod();

}

Codevoorbeeld 6: De @Deprecated annotatie is uitgebreid.

 

Snoeien

Als we het toch over deprecation hebben: er is eindelijk flink gesnoeid in Java. Het gaat dan om onderdelen als: Applets, CORBA, Java DB, VisualVM, hprof, jhat en AppleScript. De projecten Java DB en VisualVM – en overigens ook NetBeans! – gaan zeer waarschijnlijk over naar Apache.

Nu G1 de standaard garbage collector is, zijn enkele obscure GC opties verwijderd.

De 32-bit binaries zijn uit de downloadlijst verdwenen – vanaf nu alleen nog maar 64-bit binaries.

De welbekende rt.jar en tools.jar – feitelijk de hele JDK respectievelijk javac in één grote JAR – zijn vervangen door 95 modules. De .jmod files vind je in de jmods map van je JDK installatie. Het zijn – in Java 9 althans – JAR files met een andere extensie en een 4 Byte header “JM 01.00” (0x4A4D0100) hetgeen ongetwijfeld staat voor: Java Module versie 1.0. Dit implementatiedetail kan veranderen!

 

De boot gemist

Jammer, jammer: de volgende vijf JEPs hebben het niet, of niet op tijd, gehaald. In Java 10 krijgen ze weer een kans. Het gaat dan om: HTTP/2.0 client, Standardized lightweight JSON API, Money and Currency API, Local-Variable Type Inference, en, last but not least, Value Types. Over deze onderwerpen vind je veel terug in blogs die de afgelopen drie jaar geschreven zijn. Wellicht worden die blogs in de komende drie jaar weer actueel?

 

Conclusie

Hiermee sluiten we deze tweeluik over Java 9 af. Een overzicht waarin jigsaw met opzet nauwelijks aan bod kwam. Over dat onderwerp is – met name in combinatie met IDE’s, build tools en dependency managers, nog heel veel uit te vinden in de community. Wacht daar niet te lang mee: Java 7 werd end-of-life in april 2015, één jaar na het uitbrengen van Java 8. Met Java 9 wordt die lijn doorgezet: Java 8 zal per september 2018 – als je dit leest dus al over negen maanden! – geen officiële updates meer ontvangen. Stap tijdig over, en help je klant bij die overgang. Ook als je niet kiest voor modules, is er genoeg moois in de nieuwste versie van Java te vinden!

 

Referenties