Das kleinste Java-Framework der Welt.

Das kleinste Java-Framework der Welt

Dieser Beitrag zeigt, wie NinjaX zu einem abhängigkeitsfreien 96KB Java-Web-Framework-Kern wurde (Routing, Sessions, Rendering). Für einen CTO ist das wichtig, weil ein kleinerer, modularer Kern das Supply-Chain-/CVE-Risiko reduziert, die Startup-/Deploy-Geschwindigkeit verbessert und die Flexibilität bewahrt, Jetty/Module bei Bedarf hinzuzufügen.

Als Interim/Fractional CTO ist es ein wiederholbares Playbook, um geerbte Stacks schnell zu verbessern: Modularisieren, unnötige Abhängigkeiten entfernen und Kompromisse explizit machen – das beschleunigt Delivery, CI/CD und Security Reviews.

Ein kurzer Rückblick: Ninja wurde 2012 für eine andere Java-Welt gebaut

Als ich Ninja 2012 startete, sah Java – und die Art, wie wir Services bauten – ziemlich anders aus:

  • Java 8 war noch nicht die Baseline (und das Ökosystem hatte den “modernen Java”-Stil, den wir heute als selbstverständlich betrachten, noch nicht verinnerlicht).
  • “Eigenständiger Server in einem Fat JAR” war im Mainstream noch relativ neu.
  • Schnelle Startzeiten waren weniger wichtig als in der heutigen container-/serverless-lastigen Welt.
  • Lambdas und die allgemeine moderne Java-Ergonomie waren noch nicht Teil der täglichen Erfahrung.

Ninja war für diese Ära konzipiert: produktiv, praktisch und voll ausgestattet.

Warum NinjaX: ein Rewrite mit einem saubereren, kleineren Kern

Jahre später entschied ich mich, Ninja umzuschreiben als NinjaX.

Das Ziel war nicht, ein “Spielzeug”-Framework zu erstellen – es ging darum, etwas Saubereres und Schlankeres zu bauen:

  • Ein sauberer Kern mit so wenigen Annahmen wie möglich
  • Deutlich weniger Abhängigkeiten
  • Optionale Add-ons für Aspekte wie:
    • Templating
    • SQL-/Datenbankzugriff
    • JSON

Selbst dann fühlte sich NinjaX noch wie ein vollständiges Framework an: Sessions, solides Routing, Asset-Handling – die Dinge, die Sie nicht für jeden Service neu erfinden möchten.

Und persönlich war es eine großartige Gelegenheit, moderne Java-Sprachfeatures zu nutzen – und KI-Tooling einzusetzen, um das Rewrite zu beschleunigen und zu erkunden, was heute realistisch möglich ist.

Das Urteil in diesem Stadium: klein, aber gut.

Joes Herausforderung: “Was, wenn es null Abhängigkeiten hätte?”

Irgendwann unterhielt ich mich mit Joe, der Ninja mit mir mitgestartet hat und es heutzutage noch größtenteils gemeinsam pflegt.

Joe hatte schon immer eine sehr spezifische Vision: ein abhängigkeitsfreies Web-Framework.

Zu diesem Zeitpunkt war NinjaX bereits minimal – aber nicht “null Abhängigkeiten” minimal. Der Kern war noch auf ein paar Dinge angewiesen, die die meisten Java-Web-Stacks als unverzichtbar betrachten:

  • Logging (SLF4J + Logback)
  • eine JWT-Bibliothek (für Session-Handling)
  • JSON und Templating in den Kern eingebaut
  • Jetty, um tatsächlich HTTP-Anfragen entgegenzunehmen

“Null Abhängigkeiten” klang also sowohl ehrgeizig als auch leicht unvernünftig – was es zu einer großartigen Herausforderung machte, sie anzunehmen.

Schritt 1: NinjaX modularisieren (“minimal” zu einer echten Wahl machen)

Der erste Schritt war unkompliziert: Modularisieren.

Anstatt ein einzelnes Framework-Artefakt auszuliefern, bei dem jeder für Features bezahlt, die er möglicherweise nicht nutzt, wurde NinjaX zu einer Reihe fokussierter Module, zum Beispiel:

  • ninjax-core
  • ninjax-jackson-json
  • ninjax-db-jdbi
  • …und andere kleine, dedizierte Add-ons

Das ermöglichte es, eine Anwendung zu bauen, indem man nur das hinzufügt, was man tatsächlich braucht.

Allerdings brauchte man auch in dieser modularen Welt noch ein Webserver-Modul – typischerweise Jetty – um HTTP-Anfragen entgegenzunehmen.

Ergebnis (Schritt 1):

  • NinjaX wurde ordentlich modular.
  • Das minimalste “Fat JAR” war etwa 13MB groß und hing im Wesentlichen vom JDK plus einer kleinen Menge an Bibliotheken ab.

Minimal genug? Fast – aber nicht das, was Joe im Sinn hatte.

Schritt 2: Jetty durch den JDK-eigenen HttpServer ersetzen

Java liefert seit einiger Zeit einen eingebauten HTTP-Server mit: com.sun.net.httpserver.HttpServer

Die offensichtliche Frage war:

Wenn das JDK bereits einen HTTP-Server enthält… können wir Jetty komplett entfernen?

Ich habe es gebenchmarkt – mit der Erwartung “es funktioniert, aber es ist langsam” oder “es ist okay für Demos.”

Zu meiner Überraschung: Es performte wirklich gut. Gut genug, dass es realistisch wurde, es als Standard-Minimal-Runtime für NinjaX zu verwenden.

Ergebnis (Schritt 2):

  • ninjax-core nutzt den HttpServer des JDK
  • Das minimale Fat JAR schrumpfte auf etwa 4MB

Das war ein riesiger Gewinn, denn Jetty ist großartig – aber es ist auch eine bedeutende Abhängigkeit sowohl in Größe als auch in Supply-Chain-Oberfläche.

Schritt 3: Woher kommen die letzten Megabytes?

Bei ~4MB war die nächste Frage: Warum wird ein minimales Java-Web-Framework noch in Megabytes gemessen?

In meinem Fall kamen die verbleibende Größe hauptsächlich aus zwei Quellen:

  1. JWT-Bibliothek (für Session-Handling verwendet)
  2. Logging-Stack (SLF4J + Logback)

Also tat ich den unbequemen Schritt: beides ersetzen.

JWT ersetzen

Ich habe die externe JWT-Bibliothek durch eine interne Implementierung ersetzt, die für die Bedürfnisse von NinjaX geeignet ist.

Das ist ein Kompromiss:

  • Es kann das Supply-Chain-Risiko reduzieren (weniger externe Bibliotheken, weniger überraschende CVEs, die in Ihrem Stack landen – Log4j ist das warnende Beispiel, an das sich jeder erinnert). Zum Kontext: https://www.ncsc.gov.uk/information/log4j-vulnerability-what-everyone-needs-to-know
  • Aber “selber machen” bedeutet auch:
    • Einen minimalen JSON-Parser erstellen
    • Mehr Verantwortung bei uns, die Sicherheitsdetails richtig zu machen

SLF4J/Logback ersetzen

Ich habe Logback/SLF4J durch JDK Logging ersetzt.

Das entfernt eine häufige Abhängigkeitskette vollständig. Es ist auch kein wirklicher Nachteil, da Logback Bridges zu gängigen Logging-Bibliotheken hat. Wenn Sie also Logback verwenden möchten, nur zu!

Dann habe ich zusätzliche Verkleinerung angewandt, indem ich das minimizeJar-Feature des Shade-Plugins verwendet habe.

Ergebnis (Schritt 3):

  • Ein voll funktionsfähiges Web-Framework Fat JAR von ~100KB (Ist 100KB überhaupt noch ein “Fat JAR”?)
  • Nach JAR-Minimierung: 96KB
  • Nimmt weiterhin HTTP-Anfragen entgegen, unterstützt Routing, Session-Handling und Rendering.

Das ist kein “Hello World.” Das ist ein tatsächlicher Framework-Kern.

Was Sie gewinnen – und was Sie aufgeben

Diese Art der Minimierung macht Spaß, deckt aber auch echte Engineering-Kompromisse auf.

Die JDK HttpServer-Kompromisse

Der eingebaute Server ist beeindruckend leistungsfähig, aber er bedeutet auch:

  • Einige Features, die Sie in Jetty “kostenlos” bekommen, müssen von uns implementiert und gepflegt werden
    • z.B. Multipart/Form-Data-Parsing
  • Das ist mehr Code, den wir besitzen, und Parsing-Edge-Cases können sowohl fehleranfällig als auch sicherheitsrelevant sein
  • Es gibt auch Einschränkungen – z.B. ist HTTPS-Support nicht einfach auf dem Niveau vollständiger Server-Stacks (je nach Umgebung und Anforderungen)

Der wichtige Punkt: Jetty ist weiterhin eine Option. Der minimale Kern entfernt nicht die Möglichkeit, NinjaX auf die traditionelle Weise auszuführen, wenn Sie den ausgereiften Funktionsumfang eines dedizierten Servers wünschen.

96KB ist das “Kern-Minimum”, nicht Ihre vollständige App

Das 96KB-Ergebnis gilt für den minimalen Framework-Kern.

Sobald Sie echte Anwendungsfeatures hinzufügen wie:

  • Datenbankzugriff (z.B. JDBI)
  • JSON (Jackson)
  • Templating
  • andere Integrationen

…wächst Ihr Build wieder. Was in Ordnung ist – denn der Sinn der Modularität ist, dass Sie kontrollieren, wofür Sie bezahlen.

JDK Logging in der realen Welt

JDK Logging hält den Abhängigkeits-Footprint winzig, aber in vielen Organisationen:

  • Logback ist der Standard
  • Teams wollen eine einheitliche Logging-Pipeline
  • Bridges und Adapter bringen die Größe zurück

Also nochmal: Das minimale Setup ist da, wenn Sie es wollen, aber größere Systeme wählen möglicherweise bewusst einen schwereren Logging-Stack.

Zusammenfassung: Warum “kleinstes” tatsächlich wichtig ist

NinjaX endete mit etwas, das ich nicht vollständig erwartet hatte, als das Ganze begann:

Ein eigenständiger Java-Web-Framework-Kern – mit Routing, Sessions und Rendering – der in 96KB passt.

Das ist nicht nur eine lustige Statistik. Die praktischen Vorteile sind real:

  • Sie können den Code durchgängig verstehen Kleine Oberfläche bedeutet weniger versteckte Verhaltensweisen und weniger “Framework-Magic”-Fallen.
  • Sehr schnelle Startzeiten Was für moderne Deployments, CI-Workflows und Skalierungsmuster wichtig ist.
  • Schnelle, unkomplizierte Cloud-Deployments Kleinere Artefakte sind einfacher zu bauen, auszuliefern, zu scannen und auszuführen.

Und wenn Sie die Schwergewichts-Features brauchen: Sie können weiterhin Module hinzufügen (oder Jetty verwenden) und hochskalieren.

Aber jetzt ist das Minimum wirklich minimal.

Related posts