Zugneuigkeiten

Nach den komisch bis dubios formulierten „Detachment“-Neuigkeiten gefolgt von ebenso weitgehender wie vertrauensschädigender Funkstille seit Oktober 2025 (ich hatte das in einem meiner seltenen Blog-Beiträge aufgegriffen) gibt es nun endlich wieder Neuigkeiten aus dem Graal-Universum. Betitelt „Accelerating the GraalVM release train“, skizziert es die aktualisierte „Release Train“-Strategie von GraalVM.

Die Neuigkeiten in der Kurzfassung: monatliche Feature-Releases, weiterhin Pflege einer LTS-Version mit Patches, ab sofort nur noch Graal-Versionen basierend auf der dann aktuellen Java LTS-Version. GraalVM 25.0 bleibt die LTS-Version, im Mai wird GraalVM 25.1 released, und ab dann monatlich weiter mit 25.2 usw., und die Patch-Release-Änderungen sind weiterhin 3-monatlich kumuliert und fließen dann auch in das nächste Feature-Release ein. Je nach Stabilität der Feature-Releases könnte es zukünftig also eher nutzlos sein, auf die LTS-Version zu setzen, und stattdessen mindestens alle drei Monate das Feature-Release zu verwenden.

Auch sonst gibt es Einblicke in die kurzfristige Roadmap, von GraalPy über GraalJS bis GraalWasm. Besonders interessant finde ich aber „Crema“, das Native Image mit „Open World“ kombiniert – also AOT als Basis, aber wenn dynamisch Code nachgeladen wird, stehen Bytecode-Interpreter und JIT bereit, um das ebenfalls auszuführen. Es wird spannend sein zu sehen, wie das Rennen „Graal gegen Leyden“ weitergeht – die Teilnehmer nähern sich dem heiligen Gra(a)l der Performance ja von entgegengesetzten Richtungen.

Es wäre sehr hilfreich gewesen, wenn die Grundrichtung des „Detachments from the Java release train“ schon damals von Oracle publiziert geworden wäre, oder mindestens vom Graal-Team als auf GitHub explizit danach gefragt wurde. Und nachdem es die Neuigkeiten nun nur von einer der mindestens zwei beteiligten Parteien gibt, verbleibt immer noch eine gewisse Restunsicherheit. Schade.

Oracle-GraalVM-News

Ich bin mir nicht sicher, ob ich diese Neuigkeiten von Oracle bezüglich GraalVM richtig interpretiere und einordne. Wäre ich mir sicher, hätte die Überschrift beispielsweise „Oracle killt 70% von GraalVM“ gelautet, oder aber „Oracle JDK gleicht sich wieder OpenJDK an“ – je nach gewähltem Extrem der Interpretation. Aber die Wortwahl des Postings ist irgendwie verquast und Management-will-die-Neuigkeiten-nicht-klar-kommunizieren-artig. Finde ich. Auch das verlinkte Posting von 2022 das irgendwie damit zusammenhängen soll klärt die Sache nicht auf.

Ich beobachte das GraalVM-Universum ja schon einige Zeit – letztes Blog-Posting zu diesem Themenkomplex hier – weil ich die ganze Sache mindestens vom technischen Standpunkt aus für außerordentlich interessant halte, und außerdem der Use-Case „kleines Binary mit schnellem Startup“ für meine Swing-Windows-Hobby-Projekte von Vorteil wären – Konjunktiv, weil das bis heute in der (oder mindestens meiner) Praxis nicht mal ansatzweise funktioniert, aber halt in der Theorie. Konkurrierend zum „Native Image“-Feature hat Oracle ja schon vor einiger Zeit das „Project Leyden“ ins Leben gerufen, das durch Persistenzierung der JIT-Kompilate ein ähnliches Ergebnis bezüglich Startup- und Warmlaufzeiten zu erreichen. Das schien (und scheint weiterhin, wobei mit JDK 25 wohl gute Fortschritte erzielt wurden – muss ich auch irgendwann ™ mal anschauen) aber so ein typisches Mark-Reinhold-dauert-ewig-Projekt zu sein (vergleiche Modularisierung der JRE und Java-Modulsystem).

Ein paar Wendungen aus dem „Detachment“-Posting haben beinahe schon alarmierenden Charakter: „GraalVM for JDK 24 was the final GraalVM release licensed and supported as part of Oracle Java SE Products.“ oder auch „Oracle JDK 24 was the final release to include the experimental and optional Graal JIT.“ oder „GraalVM Early Adopter technology, including Native Image, is being discontinued for Java SE Product customers.“ oder „Oracle customers using the Graal JIT, either in a GraalVM distribution or as part of the integration in Oracle JDK 23 and 24, are encouraged to use the default C2 JIT instead.“

Auch alarmierend: der letzte Blog-Post des GraalVM-Teams stammt vom 11. März 2025 und befasste sich mit dem vorigen Java Release 24 – und das, obwohl die GraalVM-Hauptseite schon die Verfügbarkeit von GraalVM JDK 25 verkündet. Komische Sache.

Komisch auch: diverse Java-Newsletter haben den Oracle-Blogpost „einfach so“ ohne weiteren Kommentar und weitere Erläuterungen wiedergegeben – was allerdings auch dadurch erklärbar ist, dass die sich einfach als News-Aggregatoren verstehen und das Einordnen ihren Lesern überlassen.

Im dazugehörigen Reddit-Thread gibt es einige wenige nützliche Debattenbeiträge, die eher die Auffassung „schlecht kommuniziert von einer für den normalen Java-Nutzer unwichtigen Oracle-Untereinheit, die sich um das Produkt ‚Oracle JDK und Java SE‘ kümmert, das mehr oder weniger separat von OpenJDK und dem für Normalsterbliche relevanten Teil des Java-Ökosystems zu betrachten ist“ stärken.

Ich sage es mal so: wäre Oracle nicht Oracle, die schon regelmäßig in der Vergangenheit schlecht kommunizierte bis fragwürdige Entscheidungen rund um das Java-Ökosystem verbrochen hätten, man würde sich vermutlich gar keine Sorgen machen.

Im Moment würde ich aber eher einschätzen – oder besser vermuten, weil schon viel „educated guessing“ dabei ist – dass sich das Oracle-Annuncement tatsächlich weder auf GraalVM noch auf das Ökosystem rund um Graal-und-OpenJDK bezieht, sondern ausschließlich auf „Oracle JDK“ und „Oracle Java SE“ und deren bisheriger Graal-Integration und damit keine direkt ersichtliche Relevanz für die GraalVM-Nutzer wie Quarkus, Micronaut, Helidon, Spring und Konsorten hat. Trotzdem wäre es sehr vorteilhaft, wenn es mal ein vernünftig formuliertes Posting von der GraalVM-Seite geben würde. Denn mindestens ein Fragezeichen bleibt natürlich: die Formulierung im „Detachment“-Posting „The GraalVM team are transitioning to focus on non-Java Graal Languages including GraalPy and GraalJS.“ weist irgendwie schon auf eine Umstrukturierung der Entwicklungsschwerpunkte hin, und vielleicht ist das „Oracle Product Management“ ja der größte Geldgeber hinter dem GraalVM-Entwicklungsteam und es ergeben sich aus dieser Entscheidung längerfristige Folgen.

Zum Abschluss der Tipp an alle, die es bevorzugen, sich nicht um undurchsichtiges Oracle-Blabla zu kümmern und stattdessen „News from the bright side of Native Java“ zu lesen, empfehle ich diesen Blog-Beitrag von Bell Software zu deren „Liberica Native Image Kit“, der auf GraalVM basiert.

30 Jahre Java – Erinnerungen eines alten Sacks

Java hat kürzlich („kürzlich“ im Kontext dieses Blogs zu verstehen, wo Ereignisse gerne mal ein paar Monate bis Jahre später kommentiert werden) sein 30-jähriges Jubiläum gefeiert. Am 23. Mai 1995 erblickte das erste Release das Licht der Welt. Ich weiß noch wie heute, als ich…nein, das ist gelogen. Ich weiß nicht mal mehr, wann ich mein erstes Java-Programm wirklich mit dem JDK compiliert habe, und was es gemacht hat (ich vermute, ein Fenster per AWT geöffnet) – aber es war das JDK Version 1.0.2. Müsste ich noch auf irgendeiner CD rumliegen haben.

Mein erster Eindruck von Java war sehr gemischt. Ich kannte – im Sinne von „seriös benutzt“, nicht nur „mal gesehen“ – damals verschiedene BASIC-Dialekte, Z80- und ARM-Assembler, Smalltalk, C, C++ (CFront-Variante, Stand 1994 oder so), Turbo Pascal (inklusive der OO-Features von TP 5.5), Modula-2, Ada 83 und Ada 9X, Scheme, Lisp und Prolog. An Java gefielen mir die Interfaces, die Idee mit JavaDoc, das AWT als Chance für „write-once-run-anywhere“ auch für Anwendungen mit grafischer Oberfläche, die umfangreiche (was eben damals als umfangreich durchging…) Systembibliothek, und die prinzipielle Idee der JVM als abstrahierende, portierbare Runtime. Und natürlich der Garbage Collector. Auch die Applets, um dem drögen statischen HTML-Content endlich etwas Interaktivität beizubringen erschien als coole Idee. Unicode-out-of-the-box war sicher auch eine bemerkenswerte Idee. Und die integrierten Mechanismen zur Serialisierung von Objekten klangen auch cool. Und die vier Levels der Sichtbarkeit fanden auch mein Gefallen. Reflection schien Potenzial zu haben, auch wenn ich damals noch nicht ahnte, was man damit alles veranstalten kann. Und die Java-Variante der Exceptions fand ich cooler als die Ada-Variante, weil Java den Programmierer zu „throws“-Klauseln zwang, während bei Ada im Prinzip alles RuntimeExceptions waren und separat, also nicht erzwungen im Code, dokumentiert werden mussten – und bekanntlich laufen Code und Doku gerne mal auseinander.

Aber da war auch die andere Seite. Das Multithreading war löblich, aber gegenüber dem Tasking aus Ada doch sehr rudimentär – auf der anderen Seite aber auch ein – wenn auch unnötig kleiner – Fortschritt gegenüber pthreads. Die Basisdatentypen waren zwar Gott sei Dank genau spezifiziert (was in C bezüglich der Portabilität immer zu Schwierigkeiten führte, wenn man nicht höllisch aufpasste), aber sehr leistungsarm gegenüber beispielsweise Ada. Kein Operator Overloading. Records mussten durch Klassen abgebildet werden und hatten keine Kontrolle über die Repräsentation im Speicher. Arrays begannen immer mit Index 0. Die direkt von C übernommenen Misfeatures – == als Gleichheitsoperator und = als Zuweisung, nur short-circuit-Logik, das oberdämliche switch-case-Konstrukt – wir breiten den Mantel des Schweigens darüber. Keine Syntax für Methode-in-Methode. Kein Datentyp für Aufzählungen. Die merkwürdige Mischung aus „alles ist ein Objekt, außer Basisdatentypen“. Keine Generics wie in Ada oder C++, so dass man wieder auf Runtime-Typechecking statt Compile-Time-Safety angewiesen war. Der Interpreter der JVM ging eher gemächlich zu Sache. Der GC ebenso. Threads waren keine nativen OS-Threads, sondern wurden von der JVM simuliert („green threads“). Startup von Anwendungen war sehr gemächlich. Und das AWT war wirklich „kleinster gemeinsamer Nenner“ und mit dem Java 1.0-Sprachumfang, der bekanntlich weder innere Klassen noch deren anonyme Ausprägung unterstützte, sehr „harzig“ zu benutzen, weil man kein Event-Listener-Konzept hatte mit Callbacks, sondern im Prinzip alles ableitete und die entscheidenden Methoden überschreiben musste.

„Gemischt“ war auch das Stichwort zur Einschätzung der Lage beim damals tobenden Streit „curly-braces vs. begin-end“. Ich fand begin-end nach Pascal- oder Modula-Art nicht so gut, nach Ada-Art aber super. Und das Fehlen von Mehrfachvererbung hat auch sein Für und Wider, die Erfindung der Interfaces schien aber den Mangel ausreichend abzumildern.

Nach diesem ersten durchwachsenen Eindruck legte ich Java erst mal wieder beiseite. Im Endstadium meines Informatik-Studiums hießen die Programmiersprachen der Wahl DEC Pascal (Ada-ähnlich) in der Studienarbeit, C++ (mit dem IBM XLC als Compiler auf AIX, der leider die neuen coolen C++-Features allesamt vermissen ließ) in der Diplomarbeit, und Ada 95 für mein privates Großprojekt CDBurn. Die Idee des „Java-NC“ bzw. des ThinClients generell setzte sich nicht durch, obwohl es mit Erscheinen von Java durchaus größere Aktivitäten von großen Softwarehäusern gab, relevante Softwarepakete nach Java zu überführen. Ich nenne mal Corel Office als prominentes Beispiel. Es war schon beeindruckend zu sehen, dass WordPerfect plötzlich auf dem RISC OS-Desktop lief, und das alles bloß aufgrund der Verfügbarkeit einer Java-Runtime. Aber genau wie der Java-Prozessor von Sun und CPU-Features wie Jazelle von ARM für die beschleunigte Ausführung von Bytecode setzte sich das nicht durch. Die Zeit war noch nicht reif, die CPUs noch nicht schnell genug, die JVM noch zu lahmelig. Zuviele Abstraktionsschichten sind des Performancempfindens‘ Tod. Software als Sanduhr-Anzeige-Programm. Die Älteren fühlten sich bezüglich der fehlenden Schwuppdizität an Emacs erinnert („Eight Megabytes And Constantly Swapping“).

Aber ich hatte weiter ein Auge auf Java und nahm erfreut zur Kenntnis, dass einige wichtige Sprachfeatures in Java 1.1 nachgerüstet wurden – Stichwort innere Klassen und anonyme innere Klassen, die für die neue AWT- und Swing-Event-Listener-Mechanik essentiell waren – und an der Browser-Front Java 1.1 quasi serienmäßig zur Ausführung von Applets eingebaut wurde – HotJava und Internet Explorer waren die Vorreiter, für alle anderen gab es das Java-PlugIn. Dass Netscape dann mit „JavaScript“ etwas Verwirrung stiftete und Microsoft die übliche Embrace-and-extend-Strategie versuchte, waren bemerkenswerte aber letztlich irrelevante Randnotizen der Geschichte. Ende 1997 war auch noch das Release von Java 1.0.2 für RISC OS bemerkenswert (offiziell von Sun lizenziert und von Acorn implementiert), und Chockcino als clean-room-Java-1.1-Implementierung wurde angekündigt – in der damaligen Welt, als viele Homebanking-Lösungen auf Java 1.1-Applets setzten, war es überaus enttäuschend, dass diese Ankündigung niemals wahr wurde. Ein weiterer Sargnagel für RISC OS.

Mit Java 1.1 begann Sun auch endlich, an der Ablösung bzw. Ergänzung des leistungsschwachen AWT zu arbeiten. „Swing“ war in aller Munde, und mit Swing 1.0.2 („com.sun.java.swing“) und erst recht mit Swing 1.1.1 („javax.swing“) konnte man endlich mit einem reichhaltigen, leistungsfähigen Werkzeugkasten plattformübergreifend UIs bauen. Swing wurde damals, wohl um den Zusammenhang zu MFC, den „Microsoft Foundation Classes“, dem damaligen Benchmark für UI-Klassenbibliotheken, „JFC“ genannt – die „Java Foundation Classes“. Genaugenommen umfasste das AWT, Swing und Java2D. Und wir lernten damals, dass „lightweight“ nicht unbedingt in allen Kontexten für „schnell und speicherplatzsparend“ steht.

Ende 1998 dann das Release von Java 1.2, ein echter Meilenstein, und deshalb zurecht „Java 2 Platform“ genannt. Das Collection-Framework war ein riesiger Fortschritt (vorher war entweder „Vector“ oder „Hashtable“ allüberall genutzt), und Swing war nun endlich integrierter Teil des JDK. Allerdings dauerte es ein wenig, bis alles wirklich stabil und gut war – Java 1.2.2 war schließlich die Version der Wahl ab Mitte 1999. RMI wurde nutzbar (wenn auch umständlich mit rmic und stubs und skeletons), JIT und GC wurden performanter, das Plugin etablierte sich als Ausführungsumgebung in allen gängigen Browsern.

Ich will nicht durch alle Java-Meilensteine durchgehen, das dauert Stunden und kann auch von ChatGPT erzeugt werden. Aus der Praxis kann ich noch berichten, dass Java bei meinem ersten Arbeitgeber Anfang 1999 als Mittel der Wahl für die Implementierung eines großen Softwaresystems auserkoren wurde. Eine Entscheidung mit erheblichem Risiko damals. Hauptgründe für Java damals: die starke Unterstützung von IBM und damit die versprochene Verfügbarkeit von Integrationscode für Großrechner-Technologien wie MQ, CICS und IMS. Und die Cross-Platform-UI-Geschichte, weil die ersten drei interessanten Kunden drei verschiedene Client-Technologien am Start hatten: Windows, OS/2 und Linux (sowohl x86 als auch PowerPC). Im Nachhinein, nachdem die Software nun etwa 25 Jahre in Produktion ist, kann man wohl sagen: es war die richtige Wahl. Die Software lief erfolgreich produktiv auf Servern aller Art, von Windows über Linux und Solaris bis zu AIX. Clientseitig auf Windows, OS/2 und Linux sowohl als FatClient als auch als WebStart-Anwendung und natürlich auch lange als Applet, in einem Fall voll integriert in eine Kunden-Fachanwendung mit speziell gedengeltem Swing-L&F, so dass der „Bruch“ im Browser zwischen den beiden Anwendungen praktisch unsichtbar wurde. Erwähnenswert zum Start der Entwicklung 1999: wichtige Maßgabe am Anfang war die Anforderung eines potenziellen Kunden, dass der Code (zumindest der Client-Code für die UI) auch unter Java 1.1 laufen muss. Ich vermute, dass man deshalb noch in einzelnen Teilen des Sourcecodes einen „Vector“ findet, der Objekte per „addElement“ bekommt.

Ebenfalls erwähnen will ich „IBM VisualAge for Java“ als meine erste Java-IDE. Positiv zu nennen: es war der Grund für den Chef, für die Entwickler-Rechner die damals nahezu als „unendlich“ kategorisierte Menge von 256 MB RAM zu spendieren (AMD K6-II damals als CPU, Windows NT 4 als OS). Und das war auch dringend nötig. Die IDE war schneckenlahm, ein in Smalltalk geschriebenes Monster, und die Idee, den Code nicht im Dateisystem zu haben sondern in einem „Repository“, das im Prinzip eine Datenbank war, und dann nur per Import und Export wieder Dateien draus zu machen – gewöhnungsbedürftig. Genauso wie die Tatsache, dass die Suchfunktion nicht alle Fundstellen tatsächlich gefunden hat. Und der VCE, der „Visual Component Editor“, war mein erster Versuch mit einem GUI-Builder. Er war optimal geeignet, schon damals meine Überzeugung zu befüttern, dass GUI-Code, zumindest wenn das Layout dynamisch per LayoutManager gemacht werden soll, am besten von Hand geschrieben wird. Der eigentliche Grund, VisualAge zu verwenden – das IBM-Versprechen, in der „Enterprise-Edition“ diverse Großrechner-Integrationsbausteine einfacher verwenden zu können – war alsbald als vage Marketing-Idee entlarvt, und die Entwickler entschieden sich für Forte4Java (eine freie Version von NetBeans, damals von Sun entwickelt), JBuilder oder UltraEdit, bis sich dann alle wieder bei Eclipse 2.1 zusammenfinden konnten.

Es gab einige kritische Momente in der Java-Geschichte – die Anwandlungen von Microsoft, einen inkompatiblen Ableger zu schaffen. Die schleppende Weiterentwicklung, auch durch die finanziellen Probleme von Sun. Der beschwerliche Weg in die vollständige Open-Source-JDK-Welt von heute. Die Übernahme durch Oracle und so manche Detailverwirrung bei der Lizenzierung. Undurchschaubare Strategiewechsel bei Oracle. Aber letztlich waren das – allen Unkenrufen, auch von mir, zum Trotz – nur kurzfristige Irritationen.

Interessant in der langen Java-Geschichte finde ich den „ersten Wendepunkt“: von Konstruktion und Features her startete Java ja, obwohl in einer Server-lastigen Firma erfunden, mit starkem Client-Fokus. Fast, als ob die Erfinder dringend Microsoft ans Bein pinkeln wollten. Irgendwann, so ab Java 1.3, drehte sich das dann – auch dank Java EE und Application Servern und der Verfügbarkeit performanter JREs auf allen relevanten Plattformen – nahezu komplett in Richtung Server und Enterprise, während der Client mehr und mehr aufs Abstellgleis geschoben wurde. Und später ironischerweise durch Google mit Android und GWT wiederbelebt wurde. Man könnte sicher interessante historische Abhandlungen drüber schreiben.

Heute würde ich Java als „lebendiger denn je“ bezeichnen. Der Hotspot-JIT erzeugt sehr effizienten Code (wenn er lange genug läuft), die Garbage-Collectors sind effizient auch bei unglaublich großen Heaps und haben inzwischen unglaublich kurze STW-Zeiten, und das Tooling ist generell sehr ausgereift, dazu die Auswahl an zig stabilen und leistungsfähigen Frameworks. Die neue „release cadence“ sorgt für rasche – manchmal für mich fast zu rasche – Weiterentwicklung der Sprache, ohne die so identitätsstiftende Rückwärtskompatibilität aus den Augen zu verlieren. Es ist schon bemerkenswert, dass man heute ohne größere Handstände immer noch Java 8-Code schreiben kann, der auch problemlos noch unter Windows XP lauffähig ist. Sprachen, die Java beerben wollten wie Scala oder Kotlin sind letztlich nur Ergänzungen des JVM-Ökosystems geworden, dank der inzwischen raschen Weiterentwicklung von Java aber aus meiner Sicht außerhalb ihrer Nische eher als Ideengeber relevant. Und dank neuerer Entwicklungen wie der direkten Ausführbarkeit von Java-Files ohne vorherigen Compile-Schritt und der diversen Vereinfachungen unter dem Schlagwort „simple onramp“ nebst coolem Tooling wie JBang ist Java inzwischen selbst für Aufgaben, die klassischerweise mit dedizierten Skriptsprachen erledigt wurden, eine interessante Alternative. Keine andere Sprache ist ähnlich universell einsetzbar. Manche Sprachen sind in speziellen Bereichen die bessere Wahl, aber keine andere Sprache ist in so vielen Bereichen „good enough“, vom einfachen Skript bis zur Enterprise-Anwendung, vom Microservice bis zur FatClient-Applikation.

Und seit ich regelmäßig Konferenzvorträge von Brian Goetz schaue, habe ich auch großes Vertrauen, dass die Sprache Java weiterhin mit Augenmaß, aber kontinuierlich weiterentwickelt wird.

Wer anderer Leute „trip down the memory lane“ lesen/schauen will: der offizielle Geburtstagslivestream, die JetBrains-Jubiläums-Seite, und interessante Erinnerungsstücke bis zurück in die Gründerzeit von BellSoft-Mitarbeitern.

Neues aus dem GraalVM-Universum

Lange nichts mehr zu Graal(VM) geschrieben. Eigentlich überflüssig, diese einleitenden Worte zu verwenden, denn so wenig, wie ich hier schreibe, ist es naturgemäß bei und zu praktisch jedem Thema lange her.

Jedenfalls gibt es ein neues Release, quasi passend und synchron zum Erscheinen von Java 21. Viel hat sich getan im Java-Universum – Projekt Loom aka „Virtual Threads“ hat es nun endlich final ins Release geschafft, etwas das Projekt Panama aka „Foreign Memory and Function and Vector API“ leider nicht vergönnt war. Egal, nach dem LTS-Release ist vor dem LTS-Release, machen wir solange halt JNA. Aber ich schweife ab.

Die GraalVM-Release-Notes vermelden neben den fast schon üblichen – teilweise weiterhin dramatischen – Verbesserungen im Bereich Memory Footprint, Startup Time und Throughput zwei aus meiner Sicht ganz wichtige Neuerungen: zum einen ist man das unselige „gu“-Zeugs los geworden. Bedeutet: man hat nicht mehr ein Graal-JDK rumliegen das man per „gu“-Aufruf quasi in situ und systemspezifisch anreichert und updated – z.B. mit Truffle-Sprachen – sondern die ganze polyglotte Geschichte ist nun in „einfache“ Maven-Abhängigkeiten gewandert, d.h. man kann (bzw. sollte können – ich hab’s nicht selbst verifiziert) nun z.B. tatsächlich sinnvoll die Rhino- bzw. Nashorn-Engine tatsächlich in Rente schicken und stattdessen die Javascript-Implementierung von Graal verwenden. Das ganze läuft unter dem Namen „Truffle Unchained“. Schöne Sache, weil dadurch die ganze Geschichte mit der polyglotten Programmierung endlich anständig und erwartungskonform ins Java-Ökosystem integriert wird anstatt eine GraalVM-Insellösung zu bleiben. Man sollte nur darauf achten, die „community“-Varianten als dependency anzuziehen, es sei denn man will sich mal wieder durch eine neue Variante einer Oracle-Lizenz („GFTC“) kämpfen, und gleichzeitig immer auf der Hut sein, dass Oracle nicht mal wieder was Signifikantes daran ändert.

Zweite wichtige Geschichte: Der G1-GC ist jetzt vollständig integriert und optimiert – im Juni-Release war davon ja schon einiges zu sehen, jetzt gibt es vernünftige Ergebnisse. Die GraalVM hatte da ja Nachholbedarf v.a. im Native-Image-Bereich, was für teils erhebliche Laufzeitnachteile gesorgt hat. Nicht zuletzt durch diese Neuerung ist es gelungen, dass AOT jetzt performancetechnisch beim Durchsatz auf demselben Niveau operiert wie „heated up HotSpot JIT“ – lange Zeit war es ja eine Art Tradeoff, ob der Startup-Time-Vorteil von Native Image wichtiger ist oder der Throughput-Vorteil des HotSpot-JIT. Diese Überlegung kann man sich jetzt sparen, wenn die Benchmarks von den Graal-Entwicklern der Wahrheit nahekommen.

Für Mac-Freunde mit ARM-CPU und Linux-on-ARM-Exoten vielleicht auch noch interessant: AArch64 aka ARM 64bit ist nun auch verfügbar. War es wohl schon bei früheren Graal-Releases, ich hab’s nur nicht mitgekriegt. Aber der oben genannte G1-GC hat es wohl jetzt erst in die AArch64-Native-Image-Implementierung geschafft. Warum auch immer…da will ich nicht länger drüber nachdenken.

OpenJDK Overflow

Früher war die Welt noch einfach. Wenn man Java wollte (und ich rede jetzt nur mal von Java SE, sonst gibt das gleich wieder eine hundertseitige Abhandlung), ging man zu java.sun.com (oder gar zu java.de), hat JDK oder JRE seiner Wahl runtergeladen, und gut war. Klar, eher nicht das JRE, weil Sun irgendwann mal anfing, irgendwelche Ad-Ware damit zu bundeln, und weil es immer Installer-gebunden war und nicht als schnödes nacktes ZIP zur Verfügung stand.

Heute, im Zeitalter von OpenJDK auf der einen Seite und Oracles wöchentlich wechselnden Lizenzbedingen auf der anderen Seite schaut man sich natürlich nach einem passenden „Stamm-Provider“ eines OpenJDK-Pakets an. AdoptOpenJDK war früher mal meine erste Wahl – Auswahl zwischen HotSpot und OpenJ9, sowohl JRE als auch JDK, viele Plattformen unterstützt, sowohl x86 als auch x64. Inzwischen ist das zu „Eclipse Temurin“ von „Adoptium“ gemorphed, die Auswahl zwischen HotSpot und OpenJ9 scheint es nicht mehr zu geben, aber dafür gibt es bei IBM (wider besseren Wissens setze ich jetzt mal einen Link, wohl wissend, dass der in wenigen Monaten auf Nimmerwiedersehen einer Restrukturierung der IBM-Webpräsenz zum Opfer fällt) ein OpenJDK namens „IBM Semeru“, in den Geschmacksrichtungen „Certified Edition“ oder „Open Edition“, für den typischen IBM-Bauchladen (System/390, AIX) aber auch für Windows/Linux/MacOS als OpenJDK-OpenJ9-Bundle. Sinn? Übersichtlichkeit? Keine Ahnung. Derweil notiere ich: für die Top-IBM-Plattformen S/390 und AIX gibt es als neueste Version Java 11. Auf IBM-Plattformen meint „long term“ eben meistens die lange Zeit, bis aktuelle Versionen zur Verfügung stehen. Meine letzte gute Erfahrung mit IBM JDKs war mit Java 1.3, als auf einem linux-basierten ThinClient mit fast nacktem XServer das IBM-Java deutlich besser mit TrueType-Fonts umgehen konnte als die Sun-Konkurrenz. Lange her. Ansonsten zeichnete sich die IBM-Variante oftmals durch höheren Speicherverbrauch aus (ich erinnere mich an die Class-Sharing-Lösung in JDK 1.5, die zwar relativ gesehen pro zusätzlicher JVM-Instanz eine gute Menge Speicher sparte, aber absolut gesehen gegenüber der Sun-Lösung leider deutlich mehr verbrauchte) und dazu kamen noch abstruse Bugs im JIT-Bereich sowie irgendein dubioses Encoding-Problem im XML-Bereich, das mir den Schlaf raubte.

Ich will jetzt nicht alle OpenJDK-Distributionen aufzählen, die Menge ist inzwischen unüberschaubar, Anbieter kommen und gehen, zeichnen sich durch unvollständige Unterstützung diverser Plattformen aus, haben undurchschaubare Eigeninteressen…unterm Strich habe ich mich jetzt auf Bell Software eingeschossen, die bieten eine OpenJDK-Distribution namens „Liberica JDK“ zum Download an. Besonderheit aus meiner Sicht: alle Versionen bis zurück zu Java 8, alle für mich relevante Plattformen, sowohl JRE als auch JDK, sowohl x86 als auch x64, und als besonderen Service auch noch „Full JRE“ und „Full JDK“, ein Bundle bestehend aus Java SE und OpenJFX. Und dazu noch pre-built docker images, z.B. in einer Minimalversion mit Alpine Linux mit musl-Standard-Lib. Seit Längerem verwende ich praktisch nur noch die diversen Bell-Distributionen, alles funktioniert prächtig – ich habe aufgehört, nach besseren Alternativen zu suchen.

Eine interessante, aus meiner Sicht aber noch eher experimentelle Variante: OpenJDK in der GraalVM Community Edition. Mit dem Graal-JIT, der je nach Workload durchaus Vorteile bringen kann. Derzeit downloadbar als Version 22.1 in den Geschmacksrichtungen Java 11 und Java 17. Quasi der Außenseitertipp unter den OpenJDK-Distributionen.

Zum Schluss noch ein typisches „wann ist mir denn das entgangen“ beim Abprüfen, ob noch alle Anbieter an die ich mich erinnere im Rennen sind: was ist eigentlich JITServer? Das habe ich gerade zum ersten Mal gehört bzw. gelesen. Es gibt aber schon seriöse Infos zu diesem Thema von Januar 2020, mit Andeutungen eines existierenden Prototyps namens „JIT-as-a-Service“ schon Mitte 2019. Auch so eine IBM-OpenJ9-voll-das-fancy-Cloud-Zeugs-Geschichte. Dieses Java-Ökosystem kann einen in den Wahnsinn treiben, wenn man da auf dem Laufenden sein und bleiben will. Nächstes Mal zu diesem Thema: warum fängt man mit „Project Leyden“ an, wenn man schon Graal hat? Die Antwort wird wohl eine ähnliche sein wie bei „warum Java Modules, wenn es schon OSGi gibt“. Es kann was Gutes draus werden, aber man weiß es noch nicht, und es ist ein Haufen Arbeit auf einer ganz langen Zeitschiene. Und entlang dieser Zeitschiene liegen jede Menge Leichen aus früheren gut gemeinten Ansätzen.

Gedanken zum aktuellen Log4j-Problem

Viele haben sich schon zu unterschiedlichen Aspekten des aktuell in der Sonne der Aufmerksamkeit stehenden Log4j-Remote-Code-Execution-Problems-durch-clever-formulierten-User-Input-der-geloggt-wird geäußert. Mir fällt dabei vor allem auf, dass die meisten Kommentatoren zwar kleine Teilaspekte des zugrundeliegenden Problems meist korrekt beschreiben, es aber an abwägendem sowohl-als-auch eher mangelt. Denn m.E. ist diese Misere keins von den Problemen, die eine einfache Lösung haben.

Ich kann mit vielen der bereits von Anderen genannten Teilaspekte mitgehen. Ja, in Java kann man z.B. durch fortgeschrittene Featuritis und Libraritis und Frameworkitis sehr komplexe Lösungen bauen, die keiner mehr durchschaut. Aber das geht in praktisch jeder Programmiersprache, außer in denen, die schon unhandlich werden, sobald man versucht mäßig komplexe Probleme zu lösen. Hier ein Java-Spezifikum zu sehen ist komplett absurd. Ja, 3rd-party-Abhängigkeiten die man nicht durchverstanden hat sind potenziell gefährlich. Aber selbstgebaute Lösungen sind ebenfalls gefährlich. Ja, der Zeitdruck und die inadäquate Bezahlung in vielen Softwareprojekten ist eine der vielen Problemursachen. Aber auch ohne Zeitdruck und mit unendlichen Mitteln können Katastrophen programmiert werden. Ja, man sollte danach streben, möglichst einfache Lösungen zu bauen. Aber einfache Lösungen, die gleichzeitig problemadäquat sind, fallen auch nicht vom Himmel und brauchen Zeit und Geld – Vereinfachung ohne es zu einfach zu machen ist eine hohe Kunst.

Beim gerade aktuellen Problem kann man es sich einfach machen und sagen „was kann an Logging schon so schwer sein“. Ja dann nehmt doch einfach System.out.println, wenn das ausreichend ist. Kurze Zeit später werdet ihr feststellen, dass es ganz nützlich wäre, mehrere Logkanäle zu bedienen. Den Loglevel von außen konfigurierbar zu machen. Beim Schreiben von Logfiles jedes einzelne File nicht zu groß werden zu lassen. Auf Effizienz beim Loggen achten, also am besten asynchron arbeiten. Und die Log-Datenbank xy anbinden. Für das Loganalysetool der Wahl den Timestamp der Logmeldung parametrierbar zu machen. Ich glaube nicht, dass eine signifikante Anzahl der Log4j-Features „einfach so“ eingebaut wurden, da steckt schon jeweils ein valider Usecase dahinter. Und es hat seinen Grund, warum es nicht nur Log4j gibt, sondern auch JUL oder Logback oder tinylog – oder der gute alte IBM LogManager, falls den noch jemand kennt. Wer die Features eines Log4j nicht will oder braucht, versteckt sich hinter slf4j und überlässt dem Kunden die Auswahl der finalen Logimplementierung – aber öffnet gleichzeitig einen interessanten neuen Angriffsvektor, wenn Schadcode beim Deployment als slf4j-implementierendes Jar eingeschleust wird. Und wenn ich dann nach aufwändiger Analyse zum Schluss komme, dass Logback alle meine Wünsche erfüllt, kommt morgen eine neue Anforderung um die Ecke, die nur durch Log4j abgebildet werden kann – was dann? Anforderung ablehnen? Lib wechseln? Von vorne anfangen?

Man soll sich keinen Illusionen hingeben. Was bei Log4j passiert ist, kann mit jeder Programmiersprache, auf jedem Betriebssystem, ja sogar auf jeder CPU passieren. Spectre konnte über JavaScript im Browser ausgenutzt werden – noch so viele Schichten zwischen Quellcode und Ausführung haben uns nicht vor den Auswirkungen eines CPU-Bugs geschützt. Schon im guten alten Z80 gab es „illegale“ Opcodes, die trotzdem etwas Vernünftiges gemacht haben, als Seiteneffekt der Implementierung. Nur 8500 Transistoren, und trotzdem ein solcher „Bug“ (oder „Feature“, je nachdem wen man fragt). Der aktuelle Apple M1 Max besteht aus 57 Milliarden Transistoren. Natürlich nicht direkt vergleichbar, weil jede Menge Cache damit realisiert ist, aber auch durch Caches können ja interessante Effekte entstehen (Stichwort Seitenkanalangriff). Jedenfalls ein geeignetes Beispiel für die immens gewachsene Komplexität auf allen beteiligten Ebenen.

Ich bin auch ein Fan davon, mit möglichst wenigen Abhängigkeiten in meinen Java-Anwendungen auszukommen. Aber diese Strategie trägt eben auch nicht unendlich weit. Beispiel: wenn Persistenzierung in einer Datenbank gefragt ist, kann man von Hand JDBC machen oder Hibernate verwenden oder JPA oder JDO oder was auch immer das Framework der Wahl so mitbringt. Oder man speichert seine Daten in einer simplen Datei. Oder doch über BerkeleyDB oder SQLite? Wenn die Anforderung lautet, einen Webservice zu schreiben, beginnt man dann mit seinem eigenen Server und seiner eigenen Security-Schicht, oder nimmt man doch was von der Stange und vertraut darauf, dass die Profis es entwickelt haben? Ist eine Bibliothek, die hundert unnütze Funktionen zusätzlich zu den für mich nützlichen zehn bietet, denn notwendigerweise die falsche Wahl, wenn die Konkurrenzbibliothek mit nur zwanzig unnützen Funktionen z.B. closed source ist oder viel weniger Nutzer hat oder eine viel kleinere Community? Oder alles drei? Ist es wirklich sinnvoll, sich auf den Featureumfang der Java-Plattform zu beschränken und sklavisch auf 3rd-party-Libs zu verzichten – ist denn sichergestellt, dass die „Bordmittel“ wirklich von höherer Qualität sind als Drittbibliotheken?

Auf Basis von Post-hoc-Erkenntnissen klug daher schwätzen („hättet ihr mal nicht Log4j verwendet“ – ersetze Log4j durch „Intel-CPU“ oder „OpenSSL“ oder „PHP“ oder „Linux“ oder „Docker“ oder „IIS“ oder „Applets“) kann jeder. Aber was ist die „richtige Strategie“ im Angesicht des Ungewissen, wenn man vor einer erheblichen zu implementierenden Komplexität steht, die nicht mal schnell ein Profi-Entwickler im Alleingang und minimalen Abhängigkeiten hindengelt (und dieser Profi dann auch dauerhaft für die Wartung zur Verfügung stehen muss)? Es wird letztlich immer eine Abwägungsfrage bleiben. Tritt kein Problem auf, war die Abwägung tendenziell richtig – oder man hat einfach nur Glück gehabt.

Vermutlich ist das die einzige vielversprechend Strategie. Einfach Glück haben.

Da fällt mir ein: schon 2015 habe ich unter der schönen Wortschöpfung „Abstraktionskaskade“ diese Problematik beleuchtet. Und schon damals war ich nicht der erste.

Zwischenprojekt: KVMPortSwitcher

Meine Entwicklungsstrategie für private Projekte folgt meist einer Stack-Strategie: je älter die Idee, desto tiefer vergraben, ständig kommt neues Zeugs oben drauf, und in seltenen Fällen findet das jüngste Projekt direkt zur Reife. Und so geschah es mit dem jüngsten Spross meines bunten Straußes an Java-Projekten: KVMPortSwitcher. Wer nur den technischen Sermon lesen und den Code sehen will, darf direkt zum Projekt auf GitHub abbiegen.

Um was geht es? Seit einiger Zeit besitze ich einen „TESmart 16-port HDMI KVM Switch“ (das exakte Modell gibt es offenbar nicht mehr, aber hier ist der Nachfolger zu finden). TESmart ist der einzige mir bekannte Hersteller, der KVM-Switches mit 8 Ports und mehr zu einigermaßen bezahlbaren Preisen anbietet und trotzdem problemlos funktioniert – meine Anforderungen damals waren „4K, HDMI, 8 Ports oder mehr, keine Spezialkabel“. Dadurch schrumpfte die Zahl der zur Auswahl stehenden Geräte nach ausführlicher Recherche auf „1“.

Nun gehört alles mit 8 Ports und mehr offenbar üblicherweise der Profi-Fraktion an, und so hat dieser Switch zusätzlich einen Ethernet-Anschluss, mit dem man ihn ins LAN integrieren kann, um ihn so über IP steuern zu können. Warum würde man das wollen? Zum einen zwecks Konfiguration des Geräts. Aber vor allem zur Steuerung des ausgewählten Ports. Denn die anderen Wege, den Port zu wechseln, sind mit verschiedenen Problemen und Nicklichkeiten verbunden, die einem ganz schön auf die Nerven gehen können.

Man kann die Knöpfe an der Front des KVM-Switches verwenden. Prinzipiell keine schlechte Idee, aber beim 16-Port-Modell braucht man dabei für die Geräte 10-16 zwei Tastendrücke. Oder sogar drei, denn wenn der Display-Timeout zugeschlagen hat, dient der erste Tastendruck gar nicht dem Umschalten, sondern dem Aktivieren. Also zwei oder drei Tastendrücke, und man muss natürlich die Hand von der Tastatur nehmen.

Wie wäre es mit der IR-Fernbedienung? Anders, aber nicht besser. Wenn man sie denn mal gefunden hat (vorsichtshalber steht nur „Remote Control“ drauf, damit man sie ja nicht klar zuordnen kann), kann man zwar die Ports 2-9 durch einen Tastendruck auswählen, die Ports 1 und 10-16 hingegen nicht – was besonders bei „1“ sehr dumm ist, da drückt man auf „1“ und wundert sich, dass nix passiert, bis dem Gerät klar wird, dass keine Eingabe mehr erfolgt. Wer jetzt denkt, er könne durch zwei Tastendrücke „0“ und „1“ das Gerät überlisten, sieht sich getäuscht. auch „1“ gefolgt von „OK“ hilft nicht. Immerhin braucht es den „Aktivierungstastendruck“ wie bei den Front-Panel-Buttons nicht.

Wie wäre es mit der eingebauten Möglichkeit, über die Tastatur den Port zu wechseln? Im Prinzip eine gute Idee, aber: es funktioniert nur mit den im Gerät hartcodierten Shortcuts, und die sind eher komisch, aber natürlich so gewählt, dass sie möglichst nicht mit anderen gängigen Tastatursteuerungen kollidieren: zwei Mal Scroll Lock (aka „Rollen“), dann 1-n. Man erkennt direkt das Problem für die 16-Port-Variante: bei Rollen-Rollen-1 gibt es die von der Fernbedienung bekannte Gedenksekunde, bis klar ist, dass man wirklich „1“ meint und nicht „10“ oder „13“ und nur langsam tippt. Und noch ein Haken: das funktioniert natürlich nur, wenn die Tastatur im dafür vorgesehenen spezifischen USB-Port steckt, der diese Logik enthält – dieser Port ist aber nicht kompatibel mit allen Tastaturen dieser Welt, und so muss man manchmal auf den „nackten“ USB-Port ausweichen. Und schon ist es Essig mit der Hotkey-Funktionalität.

Insgesamt gibt es also ausreichend Gründe, alternative Möglichkeiten zum Umschalten der Ports zu suchen. Eine davon bietet TESmart selbst an mit einem kleinen Tool, das eine IP-Verbindung aufbauen kann, dadurch den aktiven Port feststellt und dann per Mausclick das Umschalten erlaubt. Schön, aber natürlich eher keine Verbesserung zu den anderen drei Varianten. Tatsächlich dauert es doch recht lange, bis das Tool endlich den „Connect“ hergestellt hat – keine Ahnung, warum. Mehrere Sekunden jedenfalls.

Nun muss man TESmart zugutehalten, dass sie das Protokoll über die IP-Schnittstelle zur Ansteuerung der Funktionalität öffentlich dokumentiert haben – leider muss man solche Dinge ja positiv vermerken, da es sich nicht um eine Selbstverständlichkeit handelt. Jedenfalls ist es denkbar einfach: IP-Verbindung herstellen, ein paar Bytes senden, fertig ist die Laube.

Ich hatte also so ein Projekt schon länger im Hinterkopf – meine Brot-und-Butter-Implementierungssprache Java schien da aber ungeeignet, weil man dort keine systemweiten Keyboard-Shortcuts beim Betriebssystem registrieren kann, und eine weitere grafische Oberfläche, wo man per Click den KVM-Switch bedienen kann, wollte ich nun auch nicht bauen. Zufällig bin ich bei meinen Streifzügen durch das Internet dann aber über eine Bibliothek namens JNativeHook gestolpert, die für alle gängigen Betriebssysteme und CPU-Architekturen native code zusammen mit etwas Java-Glue-Code zur Verfügung stellt, um solche globalen Hotkey-Listener zu implementieren. Kurz getestet, tut, also frisch ans Werk.

Der Code an sich und die UI war natürlich trivial. Kurz noch ein TrayIcon dazugeschraubt (denn für eine Anwendung, die normalerweise im Hintergrund läuft, ist es immer geschickt, noch einen Haken zur Kontrolle derselben zu haben), ausprobiert. Funktionierte – meistens. Denn es gab ein interessantes Phänomen, dass der NativeKey-Event-Dispatcher manchmal, wenn der Port umgeschaltet wurde, munter weiter denselben Event wieder und wieder feuerte. Ich bin dem noch nicht auf den Grund gegangen, aber habe einen klassischen Workaround gefunden: asynchrone Verarbeitung, der Listener löst nicht direkt den Port-Switch aus, sondern scheduled einen extra Thread, der kurz wartet und dann erst umschaltet. Das scheint das Problem zuverlässig gelöst zu haben.

Wer bis hierhin durchgehalten hat, darf jetzt zum GitHub-Projekt abbiegen. Es gibt auch ein runnable jar nebst inkludiertem jnativehook-Jar als Release 0.1.0 zum Download bei GitHub, Java 8 wird zur Ausführung benötigt. Wer also zufällig einen TESmart HDMI-KVM-Switch sein Eigen nennt und ihn in sein Heimnetz integriert (hat), kann direkt loslegen.

Auf der GitHub-Projektseite kann man auch nachlesen, was noch die TODOs sind. Konfigurierbare Port-Namen (bei 16 Ports kann man schon mal den Überblick verlieren), konfigurierbare Keyboard-Shortcuts, i18n (bis jetzt ist alles hartcodiert englisch). Und vielleicht auch noch für die old-school-Generation die Steuermöglichkeit über die serielle Schnittstelle des TESmart-KVM-Switches?

Java. Swing. Dem Wahnsinn nahe.

Seit Swing 1.0.2, als das Package noch com.sun.java.swing hieß und Java 1.1 gerade der heiße Scheiß war, programmiere ich UIs mit Java und Swing. Es ist so eine Art Hassliebe. Nach wie vor halte ich es für den einfachsten und besten Weg, Cross-Platform-Anwendungen zu schreiben, die mehr oder weniger problemlos unter Windows und Linux und MacOS funktionieren. Und an sonnigen Tagen auch unter AIX und Solaris.

Aber manchmal…manchmal…graue Haare, Stirnrunzeln beim Debugging, dem Wahnsinn nahe. Ich baue gerade an einem kleinen Projekt dessen tieferer Sinn oder Unsinn hier nichts zur Sache tun soll. Jedenfalls wird dort als Anzeigekomponente die allseits beliebe JEditorPane mit HTML-Inhalt verwendet. Das ist von jeher etwas kitzelig weil ein etwas merkwürdiges HTML-3.2-Sub-und-Superset-mit-etwas-CSS unterstützt wird, und man sollte sehr genau wissen welches HTML man reinsteckt und was unfallfrei gerendert werden kann. Immer spannend war das Thema Grafiken. Im Prinzip sollte es ja ein img-Tag mit passender URL dahinter tun. Blabla.class.getResource(„/schnickschnack.png“) liefert ja schon eine lokale File-URL zurück, was kann da schon schiefgehen.

Eine ganze Menge. Und was ist, wenn man gar keine Datei hat, sondern ein beliebiges Image in-memory? Weil das so ist, haben schlaue Menschen einen Weg ersonnen, wie man der JEditorPane – oder besser gesagt dem HTMLDocument, das über den HTMLEditorKit beim ContentType „text/html“ bekanntlich entsteht – ein beliebiges Image unterjubeln kann, indem man es in einen „imageCache“ unter der im img-Tag hinterlegten URL steckt.

Hier ein Link zu Sourcecode, der illustriert, wie das prinzipiell geht. Sehr heikel, weil es sich auf ein Implementierungsdetail verlässt, aber Swing ist bekanntlich tot…äh…stabil, und so wird das wohl auch in Zukunft problemlos funktionieren. Dachte ich.

Wenn man nun aber obigen Sourcecode mal in der IDE der Wahl compiliert und zur Ausführung bringt, erlebt man leider eine Überraschung: mit Java 7 funktioniert das noch problemlos, mit Java 8 und neuer aber nicht. Ein beherztes Debugging an der richtigen Stelle – javax.swing.text.html.ImageView.loadImage – brachte keinen Aufschluss – das Image wird korrekt aus dem Cache geholt, seine Größe wird inspiziert, es wird layoutet, und dann…wird nichts angezeigt.

Ich will es nicht zu spannend machen, denn es gibt einen Workaround. Auch wenn ich gerade überhaupt nicht verstehe, warum der was ändert, weil genau das, was er eventuell ändern könnte, im Debugging nicht nach dem Problem ausgesehen hat.

Wenn man die Zeile

"<img src=\""+localImageSrc+"\">\n" +

ändert in

"<img src=\""+localImageSrc+"\" width=100 height=50>\n" +

dann funktioniert es plötzlich prima von Java 7 bis einschließlich Java 16.

Wer auch immer das im Detail analysieren will – Freiwillige vor.

SwingHelpViewer – ein einfacher Online-Hilfe-Viewer

Seit einiger Zeit schraube ich an diversen Anwendungen auf Basis von Java Swing, die wirklich demnächst die Release-Reife erreichen sollten. Die meisten aus der Reihe „für RISC OS, aber nicht unter RISC OS lauffähig“ (da Java-basiert, und es unter RISC OS keine kompetente JVM gibt). Hier kann man ein paar wenige Details zu diesen geplanten Anwendungen nachlesen.

Wie dem auch sei, solche klassischen FatClient-Anwendungen brauchen typischerweise eine Online-Hilfe. Allein um dem Benutzer mitzuteilen, welche 3rd-Party-Bibliotheken genutzt werden und unter welcher Lizenz diese stehen.

Standard unter Java ist für solche Zwecke JavaHelp, vor Urzeiten von Sun entwickelt, aber seit fast so langer Zeit auch nicht weiter gepflegt. Diverse ungefixte Bugs, unklare Lizenzsituation (alle Sourcefiles reden von GPLv2, irgendwo steht „GPLv2 with Classpath Exception“, und es gibt auch Hinweise, dass es dual lizenziert ist sowohl unter GPLv2 als auch unter CDDL), merkwürdige indexbasierte Suche die außer Englisch recht wenig versteht, unflexibel in der Integration. Aber durchaus mit coolen Features für die damalige Zeit, inklusive einer JSP-basierten Server-Variante.

Allein die Lizenzsituation bewog mich, schnell mal was Eigenes zu bauen. Mit dem typischen „wie schwer kann das schon sein“ auf den Lippen begann ich während eines langweiligen Fußballspiels (es könnte der VfB Stuttgart involviert gewesen sein), schnell mal einen Hilfe-Viewer ähnlich des JavaHelp-Viewers zu bauen. Ungefähr 10 Stunden später hatte ich einen Viewer, der ein JavaHelp-HelpSet (.hs-Datei) parsen und sowohl TOC als auch Index eines solchen HelpSets erfolgreich darstellen. Es war also tatsächlich nicht so schwer, am längsten hat noch das XML-Gefrickel gedauert, weil ich mir in den Kopf gesetzt hatte, ganz ohne 3rd-Party-Dependencies auszukommen, und das DOM-Zeugs in Java SE ist halt sehr mit-der-Hand-am-Arm.

Code aufräumen, Icons malen und jetzt seit neuestem eine einfache Volltextsuche dazuzubauen hat nochmal ein paar Stunden gekostet, alles in allem schätze ich mal 3 PT an Gesamtaufwand. Swing ist wohl doch nicht so schlecht wie viele seit etwa 1999 nicht müde werden zu behaupten.

Das Ergebnis dieser Bemühungen kann man auf GitHub inspizieren. Und damit es wirklich von jedem auf welche Weise auch immer verwendet werden kann, habe ich als Lizenz die „Unlicense“ ausgewählt. Ist bestimmt auch wieder inkompatibel mit irgendwas, aber es ist die freieste Lizenz, die ich gefunden habe.

Wird es für den SwingHelpViewer eine Weiterentwicklung geben? Natürlich liegen einige Erweiterungen auf der Hand (indexbasierte Suche z.B. über Apache Lucene, ein Bookmark-System, Unterstützung für weitere JavaHelp-Features wie „merged help sets“ und Direkthilfe-Action und Hilfe-Cursor und so, Unterstützung für andere Hilfedaten wie CHM, HLP oder Eclipse Help, Einbindung anderer Anzeigekomponenten die mehr von HTML und CSS verstehen als JEditorPane), aber der jetzige Zustand dürfte für meine Zwecke wohl reichen.

Wer die Herausforderung sucht, könnte z.B. mal schnell eine Java 1.2-kompatible Version des Codes bauen. Im Moment läuft alles ab Java 7, wenn man den Diamond Operator rauswirft müsste Java 5 gehen, wenn man die Generics rauswirft Java 1.4, und wenn man das XML-Parsing anders löst (wer erinnert sich noch an crimson.jar…), dürfte man bei Java 1.2 angekommen sein. Der Code ist sehr „old-school“, und Swing ist ja im Prinzip seit Java 1.2 unverändert.

Was GitHub angeht, ist SwingHelpViewer mein erstes dort publiziertes Projekt. Wenn ich die jetzige Geschwindigkeit beibehalte, werden meine über die Jahre entstandenen programmiertechnischen Ergüsse spätestens bis 2050 komplett auf GitHub gelandet sein.

Stümpern mit Niveau

Ab und zu – die Frequenz ist uneinheitlich, die Rahmenbedingungen unbekannt – neige ich dazu, irgendein IT-Thema mal genauer zu betrachten und ein paar Experimente durchzuführen. Quasi um immer wieder das „innere Komplexitätsgefühl“ neu zu justieren. Ich stelle eine gewisse Häufung beim Thema „Protokolle“ fest, vielleicht die masochistische Ader die mich schon zur Implementierung einer CD-/DVD-/BluRay-Brenner-Software gebracht hat. Ziel ist immer, bei einer Sache von Quasi-Null-Wissen auf mindestens 3 zusätzliche Seiten im persönlichen Handbuch des nutzlosen Wissens zu kommen. Quasi qualifiziertes Halbwissen, das jede Fachdiskussion unter einer Stunde locker überlebt.

Diesmal das Thema: IPP, das „Internet Printing Protocol“, das im Rahmen einer Diskussion im Kontext meines Lieblingsbetriebssystems RISC OS eher zufällig aufgekommen ist. Früher dachte ich, IPP sei quasi nur „Druckdatenstrom per HTTP an den Drucker übermitteln“. Sowas wie „JetDirect über HTTP“. Oh Mann, lag ich falsch. Oder besser: es ist eine sehr simplifizierte Beschreibung der Wirklichkeit, und qualifizierte mich damit als „Quasi-Null-Wissenden“.

In Wahrheit hat IPP interessantes Potenzial für die RISC OS-Welt, nämlich „driverless printing“. Man kann per standardisierten Requests den Drucker zu seinen Eigenschaften befragen (Papierzufuhroptionen, Nachbearbeitungsoptionen, Duplex oder nicht und wenn ja wie), zu seinem Status (Tonerstand/Tintenstand, Papierstau etc.) und es gibt ein plattformübergreifendes Rasterformat („PWG Raster Format“ – PWG ist die „Printer Working Group“, die hinter IPP steht), das zum Druck verwendet wird. Zusätzlich vermelden netzwerkverbundene Drucker ihre Anwesenheit über das Bonjour-Protokoll, aber auch direkt verbundene USB-Drucker werden unterstützt. Das Gesamtkunstwerk nennt sich IPP Everywhere. Also die Lösung aller Probleme der Art „klar gibt es da diesen Drucker XYZ, aber für Betriebssystem ABC gibt es leider keine Treiber“ – heute behilft man sich da (zumindest im RISC OS-Markt) mit der oberen Preisklasse der PostScript- und PDF-fähigen Drucker, muss aber immer noch tricksen um mit speziellen Druckerfeatures arbeiten zu können.

Die IT wäre nicht die IT, wenn nicht zwei konkurrierende andere Lösungen gleichzeitig um die Gunst des Anwenders (interessiert den das eigentlich?) buhlen würden: AirPrint (von Apples Gnaden) und Wi-Fi Direct Print. So weit, so ärgerlich. Zurück zu IPP. Dessen Charme ist die Verfügbarkeit eines gut dokumentierten Standards, bei den beiden Konkurrenten sieht es da nicht ganz so gut aus, dafür waren diese früher am Markt und sind (deshalb?) deutlich weiter verbreitet – IPP Everywhere wird heute eigentlich nur von einer Reihe von HP-Druckern unterstützt.

Wie auch immer, ich fasste ein Ziel ins Auge: mit meinem IPP-fähigen (aber natürlich nicht IPP Everywhere-kompatiblen, weil Baujahr 2011) Oki C530 ein PDF zu drucken, indem über IPP der PDF-Datenstrom an den Drucker geschickt wird.

Wie anfangen? Der IPP-Guide erzählt von 40 Spezifikationen mit insgesamt fast 2000 Seiten, das schien jetzt etwas ambitioniert. Aber es gibt JIPP, eine Java-Implementierung von IPP von HP, verfügbar auf GitHub. Unter der guten MIT-Lizenz. Da waren Beispiele dabei. Wie schwer kann es also sein?

Erste Hürde: das Github-Projekt hostet nur Source-Releases. Kein Problem, man kann ja selber bauen. Stellt sich raus: ist Kotlin-Code, gebaut mit Gradle. Hm. Bin ich jetzt für beide Dinge nicht so der Experte, vielleicht ein Experiment für ein anderes Mal. Google führt mich zu einer sinnvollen Paketierung des Codes inklusive aller seiner Abhängigkeiten (wie der Kotlin-Runtime – auch nicht gerade schmal das Dingens). Also schnell ein IDE-Projekt aufgesetzt und los geht’s. Ich arbeite mit Version 0.6.16 – nur falls jemand ein paar Jahre später über diesen Artikel stolpert und bis hierher durchgehalten hat.

Das jprint-Beispiel (inzwischen übrigens – ohne meinen Input und erst vor wenigen Stunden! – signifikant verbessert, an einigen der Stolperfallen) erst mal als Basis genommen. Ein PDF an die bekannte IPP-URL des Druckers geschickt. Eine dubiose Exception war das Ergebnis (aus dem HTTP-OutputStream: java.io.IOException: Error writing request body to server) – wies darauf hin, das irgendwas mit der URL nicht stimmte. OK. Ein paar Varianten durchprobiert (offenbar gibt es typische Implementierungen der Form ipp://irgendneip/ipp und ipp://irgendneip/ipp/lp und http://irgendeineip/ipp und http://irgendeineip:631/ipp – an dieser Stelle die Nebenerkenntnis, dass IPP schlicht HTTP-mit-631-als-Default-Port ist). Manchmal den gleichen Fehler, manchmal einen anderen Fehler (java.net.SocketException: Software caused connection abort: recv failed). Dubios. Weiter rumprobiert. Stellt sich raus: auch bei derselben URL kommt manchmal die eine Exception, manchmal die andere. Aha. Dann doch kein Connection-/URL-Problem? Mal den Code genauer anschauen. Als Transportmechanismus wird eine Klasse namens „IppPacket“ verwendet. Da gibt es einen alternativen Konstruktor, der auch eine „ippVersion“ (ein int wie es sich gehört) entgegen nimmt. Könnte es sein, dass mein Drucker (aus der Zeit von IPP v1.1) mit dem Default von JIPP (IPP v2.x) einfach nicht klarkommt? Mehrfaches Ausführen des Codes mit stets derselben URL führt tatsächlich manchmal zu einer Exception-losen Verarbeitung des Kommandos mit der Fehler-Rückmeldung „server-error-version-not-supported“, was den Verdacht erhärtet.

Etwas später ist klar: dummerweise erlaubt der Konstruktor von IppPacket, der eine explizite Versionsangabe erlaubt, nicht dieselben Typen an Restparametern, insbesondere nicht den „Operation“-Enum, sondern auch nur einen int. OK, ein paar RFCs zum IPP gelesen und herausgefunden, dass das Kommando getPrinterAttributes 0x0B ist und printJob 0x02. Minuten später sehe ich, dass der Enum innen drin die ints eh auch definiert hat. Grmpf. Ist bestimmt so ein Kotlin-Automatismus. Bleibt die Frage: wie spezifiziert man jetzt in einem int eigentlich „1.1“ als Version? Na per 8+8 Bit natürlich. Also 0x0101 für meine Zwecke. Prima. Erstmal getPrinterAttributes probieren statt gleich das PDF zu schicken. Das Kommando wird jetzt zuverlässig verschickt, aber JIPP kann das Resultat nicht unfallfrei parsen – ob das eine Schwäche von JIPP ist oder eine Abweichung von der Spezifikation seitens meines Druckers ist noch ungeklärt, aber Gott sei Dank hat der JIPP-Programmierer bei solchen Parse-Fehlern daran gedacht, die Rohdaten des Replys, der nicht parsebar waren, herauszudumpen. Also konnte ich einfach inspizieren, was mein Drucker so als Fähigkeiten zurückmeldet.

Nachdem das Kommando-Schicken nun völlig problemlos funktionierte, machte ich mich an die eigentliche Aufgabe, den Druck eines PDFs – der Oki unterstützt sowohl direktes Drucken per PostScript als auch per PDF, und PDFs finden sich auf der Platte entschieden leichter. Ein einseitiges Dokument identifiziert und per offiziellem PDF-MIME-Type „application/pdf“ zum Drucker geschickt. Kommt zurück: „client-error-attributes-or-values-not-supported“. Eine sprechende Fehlermeldung! Sehr schön. Inspektion der bei getPrinterAttributes zurückgemeldeten Fähigkeiten zeigt: „application/pdf“ wird auch gar nicht akzeptiert, nur „application/octet-stream“, „application/vnd.hp-PCL“ oder „application/postscript“. Einfach mal „application/octet-stream“ geraten, und siehe da: es wird gedruckt.

Na das war ja einfach.

Zu klärende offene Fragen: 1. Nützt mir das jetzt was für IPP unter RISC OS? 2. Wie erzeuge ich möglichst einfach das PWG-Raster-Format aus einem beliebigen Quellformat? 3. Warum drucke ich nicht einfach PDF über einen CUPS-Druckerserver, der ja auch IPP out-of-the-box kann?

Antworten: 1. Noch nicht. 2. Sieht nicht so schwierig aus, auch keine ultrakomplexe Kompression, sondern simples PackedBits-Format und anständig dokumentiert. 2a. Aber auch JPEG wird als Format im Standard zwingend vorausgesetzt – noch einfacher! 3. Weil das ja jeder kann.

Interessanter Beifang: AppImage als Paketiervariante für Linux-Anwendungen. Spannend, kannte ich noch nicht.