“Je kunt dus kiezen tussen de blauwe en de rode pil. Als je de blauwe pil neemt, dan word je wakker in je bed en blijf je ontwikkelen tegen Long Term Support-releases, met elke drie jaar een grote migratie. Neem je de rode pil, dan ga je wat beleven! Je kunt dan genieten van de nieuwste mogelijkheden en performance-optimalisaties, met elke zes maanden een kleine migratie. De keuze is aan jou; ik bied je enkel de waarheid.”
Auteur: Hanno Embregts
Mark Reinhold is niet alleen fan van ‘The Matrix’, hij is ook de Chief Architect van de Java Platform Group. En tijdens zijn keynote op Devoxx legde hij op deze manier de nieuwe release cadans van Java uit. Wat was ik blij dat we bij Translink nét onze software naar Java 11 hadden gebracht. In de toekomst zouden we nu, net als Neo, de rode pil kunnen kiezen!
Zes weken daarvoor schoven we het kaartje “Upgrade naar Java 11” naar de kolom “In Progress”. Java 11 was diezelfde dag uitgekomen, dus we waren aan de vroege kant. Maar we konden eigenlijk niet anders. We wisten al dat eind 2018 een groot nieuwbouwproject zou starten en tot die tijd hadden we wat ruimte op de backlog. Bovendien zou in januari 2019 de support op Java 8 voor commerciële gebruikers vervallen; upgraden in die maand zou, wegens het geplande nieuwbouwproject, slecht uitkomen.
Het eerder genoemde nieuwbouwproject heeft alles te maken met de uitdagingen die Translink voorziet in de nabije toekomst. Als verwerker van meer dan 2,5 miljard transacties per jaar hebben we onze sporen verdiend als hart voor het betalingsverkeer in het openbaar vervoer. Maar onze reizigers willen meer betalingsmogelijkheden, zodat ze zich vrij(er) kunnen bewegen. Ze willen niet alleen reizen met een chipkaart, maar ook met een creditcard, smartphone of smartwatch. Translink bereidt deze toekomstige mogelijkheden voor door software te ontwikkelen die deze betaalmiddelen ondersteunt. Zo’n innovatief stuk software verdient beter dan een Java-versie van ruim vijf jaar oud.
Niet verwonderlijk dus dat de ondersteuning per januari 2019 afloopt. Dat wil zeggen: Oracle brengt in januari 2019 de laatste vrij beschikbare update voor Java 8 uit. Daarna zijn de updates alleen nog beschikbaar voor houders van een commerciële licentie. Met zo’n licentie ben je nog tot maart 2022 onder de pannen, maar dat zal wel financiële gevolgen hebben.
Daarnaast mis je natuurlijk allerlei nieuwe mogelijkheden, zolang je op Java 8 blijft. Denk o.a. aan het modulesysteem uit Java 9, var als typeaanduiding uit Java 10 en het direct uitvoeren van één .java-bestand uit Java 11 . Verder is Java 11 in de nieuwe release cadans een Long Term Support-versie, met gratis publieke updates t/m 2023. Dat maakt Java 11 een interessante release om naar te upgraden, omdat je vanaf daar beide upgradestrategieën, die eerder in de inleiding genoemd werden, kunt uitvoeren.
[INFO] --- maven-compiler-plugin:3.8.0:compile (default-compile) @ first-component --- [INFO] Changes detected - recompiling the module! [INFO] Compiling 70 source files to C:\dev\translink\first-component\target\classes WARNING: An illegal reflective access operation has occurred WARNING: Illegal reflective access by lombok.javac.apt.LombokProcessor to field com.sun.tools.javac.processing.JavacProcessingEnvironment.discoveredProcs WARNING: Please consider reporting this to the maintainers of lombok.javac.apt.LombokProcessor WARNING: Use --illegal-access=warn to enable warnings of further illegal reflective access operations WARNING: All illegal access operations will be denied in a future release
De componenten die we naar Java 11 hebben gebracht, bevinden zich in de mid-office van Translink. Ze voorzien de OV-chipkaart-website van de benodigde gegevens en logica om bijvoorbeeld reishistorie te bekijken of saldo te bestellen. Ze zijn gebouwd op basis van Spring 5. Verder maken we voor de continuous delivery gebruik van Maven, Jenkins 2, SonarQube en Docker Compose. In Figuur 1 staat in meer detail opgesomd welke dependencies we in deze componenten voornamelijk gebruiken.
We zijn begonnen met het downloaden van JDK 11 en het updaten van onze IDE. Daarna hebben we, mede ingegeven door een uitstekend artikel van Leonardo Zanivan, voor elk softwarecomponent de volgende fases doorlopen:
Heb je, net zoals wij, voldoende tijd om de upgrade uit te voeren, dan kun je deze fases gelijktijdig of in ieder geval direct achter elkaar uitvoeren. Is je tijd meer gefragmenteerd, dan is het ook mogelijk om steeds maar één fase te doen, omdat na iedere fase een werkbare situatie blijft bestaan.
In de meeste gevallen is dit de meest eenvoudige stap: software die met Java 8 gecompileerd is, draait namelijk prima op Java 11, zolang je niets uit Java EE of CORBA gebruikt. In het ergste geval moet je een aantal dependencies toevoegen, zoals java.activation of java.xml.bind.
Als je vervolgens je softwareproject met Java 11 gaat compileren, dan zal je de melding tegenkomen die in Listing 1 staat weergegeven. Al sinds Java 9 verschijnen waarschuwingen als de interne packages sun.* en *.internal.* worden gebruikt, om de gebruiker te laten weten dat deze packages in de toekomst niet meer te gebruiken zijn.
In dit geval probeert Lombok de genoemde interne packages te gebruiken; dit hebben we opgelost door de laatste versie van Lombok te gebruiken. Als de interne packages vanuit je eigen code worden gebruikt, zal je voor die functionaliteit een alternatief moeten vinden. Overigens kan dit probleem ook optreden als transitieve dependencies interne packages benaderen. In dat geval kun je de transitieve dependency overschrijven, zoals staat weergegeven in Listing 2.
Op dit punt werkt je component al op Java 11. Je gebruikt alleen nog niet het modulesysteem. Dit kun je activeren door in src/main/java een module-info.java aan te maken. Deze bevat de modulenaam, een lijst van benodigde modules van buitenaf (requires) en een lijst van packages die compile-time en/of run-time beschikbaar zijn voor andere modules (respectievelijk exports en opens). Je IDE zal je overigens op basis van de pom.xml snel kunnen vertellen voor welke modules je requires toe moet voegen.
<plugin> <groupId>org.jvnet.jaxb2.maven2</groupId> <artifactId>maven-jaxb2-plugin</artifactId> <version>0.14.0</version> <dependencies> <dependency> <groupId>javax.xml.bind</groupId> <artifactId>jaxb-api</artifactId> <version>2.4.0-b180830.0359</version> </dependency> <dependency> <groupId>org.glassfish.jaxb</groupId> <artifactId>jaxb-runtime</artifactId> <version>2.4.0-b180830.0438</version> </dependency> </dependencies> </plugin>
Onderweg kwamen we allerlei obstakels tegen, die we hierna in meer detail behandelen. Wie weet kan onze aanpak jouw project helpen.
Sinds Java 9 is het niet meer mogelijk een package te gebruiken die in twee modules bestaat. Java gebruikt hiervoor het module path. Directe dependencies komen altijd op het module path uit, maar het gedrag van transitieve kan verschillen (afhankelijk van de implementatie). Maven zet transitieve dependencies bijvoorbeeld op het classpath, terwijl IntelliJ IDEA ze op het module path zet. Zo kan het voorkomen dat je component compileert via Maven, maar dat IntelliJ je om de oren slaat met meldingen over ‘split packages’.
Er bestaan verschillende, tijdelijke oplossingen die je kunt proberen, maar de enige definitieve oplossing is het verwijderen van de package split. Hoe die oplossing eruit ziet, is afhankelijk van de situatie, maar kan simpeler uitpakken dan je van tevoren misschien denkt. Wij kregen de melding bijvoorbeeld op de package javax.servlet, die we hebben opgelost door één referentie naar de Servlet-klasse in JavaDoc te vervangen door de tekst “servlet”. Toen was de dependency niet meer nodig en het probleem opgelost.
Voorafgaand aan de upgrade gebruikten we openjdk:8-jre-alpine als basisimage voor onze Docker-containers. Maar Java 11 heeft momenteel helaas geen stabiele build voor Alpine Linux; daardoor zijn we voorlopig genoodzaakt om het veel grotere image openjdk:11-jre-slim als basis te gebruiken. Mogelijk komt er voor Java 12 wel een stabiele release, dat is nog even afwachten.
Misschien kwam het doordat we zo vroeg waren gestart, maar de ondersteuning van SonarJava en Jenkins voor Java 11 liet even op zich wachten. Sonar kan nu Java 11-bestanden scannen, maar zelf nog niet op Java 11 draaien. Jenkins had de Java 11-ondersteuning pas begin december aangekondigd. Een direct gevolg was dat we een aantal componenten een tijdje op Java 10 hebben gehouden. Nu de ondersteuning er is, kunnen we de upgrade voor die componenten afronden.
Voor een paar componenten wachten we dus nog op betere Java 11-ondersteuning. Maar de rest van de upgrade is voltooid en dus zijn we klaar om nieuwe functionaliteit te gaan opleveren. Wel zijn er een aantal zaken die we een volgende keer anders gaan aanpakken!
We waren het modulariseren bewust begonnen bij een component van gemiddelde grootte, met een gemiddeld aantal dependencies. De reden daarvoor was dat we onze ervaringen van de upgrade van dit component wilden gebruiken om in de volgende Sprint Planning meer zekerheid te kunnen geven over de doorlooptijd van het geheel. Dat kwam echter niet goed uit de verf, omdat de modularisatie van een component pas voltooid is als de dependencies van dat component óók in modules zijn opgedeeld. Hoe kun je immers in de module-info.java refereren naar een module die nog niet bestaat? Zo kwam het dat we eerst zes andere componenten moesten modulariseren voordat bij ons proefcomponent de upgrade voltooid was. En daarmee bleek het positieve effect op de Sprint Planning helaas nihil. Een volgende keer gaan we eerst de dependencies analyseren en starten bij een component dat weinig dependencies op andere projecten heeft, maar zelf wél als dependency gebruikt wordt.
Vanwege de ruimte op de backlog begonnen we de upgrade zelfs op de dag dat Java 11 uitkwam. Ook dat doen we een volgende keer anders. Veel dependencies werkten wegens het gebruik van interne packages in die eerste weken nog niet op Java 11. We konden niet veel anders dan het probleem melden bij de maker en de waarschuwing tijdelijk negeren. Naarmate de tijd verstreek, kwamen steeds meer libraries met updates, waardoor de waarschuwingen verdwenen. Mooi natuurlijk, maar tegelijkertijd bekroop ons het gevoel dat het beter was geweest om na de release van Java 11 een week of drie, vier te wachten voordat we aan de upgrade waren begonnen. Voor een volgende upgrade zijn we dus gewaarschuwd.
Op het moment dat deze editie van Java Magazine verschijnt, is Java 11 al ruim vier maanden beschikbaar. Daardoor kun je op veel meer Java 11-ondersteuning rekenen dan wij dat konden. En ook met weinig ruimte op je backlog kun je aan de upgrade beginnen, door steeds één van de drie stappen apart uit te voeren. Doe dit als het je uitkomt.
Mocht je overigens elke drie jaar een dergelijke migratie gaan doen (naar de LTS-releases – ‘de blauwe pil’), dan kun je het werk wat spreiden door je software tussendoor te draaien met een non-LTS-release en alvast wat aanpassingen te doen. Ga je elke zes maanden migreren – ‘de rode pil’ – geef dan het regelmatig bijwerken van je tools en dependencies prioriteit.
Wat je ook gaat kiezen, vergeet niet te genieten van de nieuwe features die je met de nieuwe Java-versie tot je beschikking krijgt. En… schep er vooral over op. Want (zie Figuur 2) dat heeft de Chief Architect van de Java Platform Group ons immers gevraagd.