Legacy-Java-Projekte scheitern bei Solo-Freelancern selten an fehlender Architektur-Eleganz. Sie scheitern daran, dass Security- und Datenschutzkosten erst sichtbar werden, wenn der Kunde schon Migration, Fixpreis und Termin erwartet. Meine strittige Position: Für Einzelkämpfer ist eine konservative Modernisierung oft sicherer als der große Sprung auf Cloud, Container und neue Frameworks, weil jede zusätzliche Plattform neue Nachweise, Logs und Angriffsflächen erzeugt.
Die teuersten Risiken sitzen nicht im alten Code, sondern in den neuen Beweisen
Ein altes Java-8-System wirkt unsicher, weil es nach Legacy riecht. Das kann stimmen, aber der erste gefährliche Irrtum ist: Modernisierung selbst erzeugt automatisch Sicherheit. Sie tut es nicht, weil neue Build-Pipelines, Container-Images, Telemetrie, Managed-Datenbanken und externe Scanner plötzlich personenbezogene Daten, Secrets und Abhängigkeiten sichtbar machen, die vorher nur auf einem alten Server lagen.
Der Beitrag Legacy Java modernisieren Warnsignale Risiken Strategie benennt reale Warnsignale, doch ich würde sie für Solo-Freelancer anders gewichten, weil ein einzelner Entwickler nicht gleichzeitig Code-Auditor, Datenschutzbeauftragter, Plattformteam und Incident-Responder sein kann. Ein ungepatchtes Struts 2 ist ein Problem; ein hektisch gebautes Ersatzsystem mit Debug-Logs, falsch gesetztem CORS und dauerhaft gespeicherten Testdaten ist ebenfalls ein Problem, nur moderner verpackt.
Konkretes Beispiel: Du ersetzt eine alte Servlet-Anwendung durch Spring Boot 3.2 oder 3.3, hängst Actuator an, nutzt Micrometer 1.12, schickst Metriken via OpenTelemetry Protocol OTLP/gRPC an einen kostenlosen Cloud-Endpunkt und aktivierst strukturierte JSON-Logs. Das klingt sauber. Der Datenschutz-Trade-off entsteht, sobald Query-Parameter, E-Mail-Adressen oder interne Kundennummern in Logs landen, weil Observability-Werkzeuge standardmäßig mehr Kontext wollen als dein Auftragsverarbeitungsvertrag erlaubt.
Eine selbst gemessene Zahl aus vielen kleinen Legacy-Checks ist ernüchternd: In einem typischen Maven-Projekt mit 80 bis 140 direkten und transitiven Artefakten findet OWASP Dependency-Check 9.x häufig zweistellige CVE-Treffer, aber nur ein Teil ist über den konkreten Codepfad erreichbar. Diese Zahl darf nicht als Paniksignal dienen, weil CVSS allein keine Ausnutzbarkeit in deiner Anwendung beweist.
Vendor-publiziert ist dagegen die Realität der Java-Laufzeit: Oracle JDK 8 Public Updates endeten für viele kommerzielle Nutzungen bereits 2019, während Eclipse Temurin und andere OpenJDK-Distributionen eigene Supportfenster haben. Das bedeutet nicht, dass Java 8 sofort verboten ist; es bedeutet, dass du dem Kunden erklären musst, wer Sicherheitsupdates liefert, wie lange, und zu welchen Lizenzbedingungen.
Ich würde nicht als Erstes einen Kubernetes-Cluster aufsetzen, auch nicht mit k3s, Minikube oder einem günstigen Managed-Angebot, weil Kubernetes zwar Deployment-Probleme löst, aber für einen Solo-Freelancer zusätzlich NetworkPolicies, Secrets, RBAC, Ingress-TLS, Image-Scanning, Backup-Strategie und Audit-Logs erzeugt. Wer diese Disziplinen nicht betreiben kann, kauft sich mit Kubernetes oft mehr Verantwortung als Sicherheit.
Ein kleiner Sicherheits-Schnitt schlägt die komplette Plattform-Migration
Meine bevorzugte Reihenfolge ist unbequem: erst Geheimnisse, Abhängigkeiten, Logs und Datenflüsse stabilisieren, dann Frameworks anfassen. Das widerspricht dem Impuls, sofort von Java 8 auf Java 17 oder 21, von Spring 4 auf Spring Boot 3 und von Tomcat 8 auf Tomcat 10.1 zu wechseln. Der Grund ist simpel: Eine Plattformmigration verändert so viele Variablen, dass du Datenschutzfehler schlechter isolieren kannst.
Ein pragmatischer Sicherheits-Schnitt beginnt mit Maven oder Gradle. Für Maven sind maven-enforcer-plugin 3.4.1, versions-maven-plugin 2.16.2, OWASP Dependency-Check 9.2.x mit failBuildOnCVSS und Syft 1.x für SBOMs handhabbar. Für Gradle helfen Gradle 8.x, dependency-locking und org.owasp.dependencycheck. Für Container-Images sind Trivy 0.50+ und Grype sinnvoll, aber nur, wenn du Images wirklich auslieferst.
Ein sinnvoller Grenzwert zum Justieren ist failBuildOnCVSS=7: Hochkritische Treffer stoppen den Build, mittlere Treffer werden dokumentiert. Das ist keine Naturkonstante, sondern ein Tuning-Wert, weil ein Solo-Freelancer sonst Tage mit irrelevanten transitiven Meldungen verliert. Eine weitere arbeitsfähige Vorgabe ist ein Log-Retention-Ziel von 14 Tagen für Entwicklungs- und Testlogs; dieser Wert ist bewusst knapp gewählt, weil längere Aufbewahrung ohne Such- und Löschkonzept das Datenschutzrisiko erhöht.
So kann ein minimaler Check aussehen, der auf einem normalen Rechner läuft und ohne eigene Infrastruktur auskommt:
#!/usr/bin/env bash set -euo pipefail mvn -q -DskipTests verify mvn -q org.owasp:dependency-check-maven:9.2.0:check \ -DfailBuildOnCVSS=7 \ -Dformat=HTML syft dir:. -o cyclonedx-json > sbom.cdx.json trivy fs --severity HIGH,CRITICAL --exit-code 1 . grep -RInE "(password|secret|token|Authorization)" src || true
Das ist nicht perfekt, aber es läuft lokal, erzeugt ein CycloneDX-SBOM und stoppt grobe Fehler. CycloneDX 1.5 ist hier besser als eine selbst gepflegte Excel-Liste, weil Kunden, Scanner und CI-Systeme das Format lesen können. SPDX 2.3 ist ebenfalls etabliert; CycloneDX gewinnt bei Anwendungsabhängigkeiten oft, weil die Tool-Unterstützung im Java-Ökosystem direkter ist.
Bei Privacy ist die wichtigste Modernisierungsarbeit meist unspektakulär: Entferne personenbezogene Daten aus Logs, bevor du Logging modernisierst. Logback 1.4, SLF4J 2.0, Log4j 2.22, Spring Boot Actuator, Micrometer und OpenTelemetry können viel; sie können aber auch zuverlässig Daten verteilen, die nie hätten gespeichert werden sollen. Eine gemessene Größe aus einem Audit solltest du selbst erheben: Wie viele Logzeilen enthalten E-Mail, Kundennummer, Session-ID oder IP-Adresse? Schon 1 Treffer pro 1.000 Zeilen ist bei produktiven Logs ernst, weil Logs oft länger und breiter verfügbar sind als die Primärdatenbank.
Cloud-Modernisierung gewinnt selten, wenn niemand die Cloud betreibt
Die harte Entscheidung lautet oft: Option A: In-place-Härtung auf einem bestehenden VM- oder Root-Server gegen Option B: Migration auf Managed Cloud mit Containern. Option A gewinnt, wenn das System klein ist, der Kunde wenige Releases braucht und du die Umgebung kennst; sie kostet technische Schulden, manuelle Disziplin und begrenzte Skalierung. Option B gewinnt, wenn Backups, TLS, Datenbank-Patches und Monitoring vertraglich gebraucht werden; sie kostet laufende Plattformgebühren, Datenschutzprüfung, IAM-Komplexität und mehr Betriebswissen.
Ich halte Option A für unterschätzt, weil ein gehärteter Debian-12-Server mit systemd, Nginx 1.24, OpenJDK 17, unattended-upgrades, UFW oder nftables und PostgreSQL 15 für viele kleine Legacy-Java-Anwendungen transparenter ist als ein halbfertiges Cloud-Setup. Diese Aussage ist streitbar, aber begründet: Transparenz senkt bei Solo-Freelancern die Fehlerquote, weil weniger Ebenen untersucht werden müssen, wenn etwas ausfällt oder Daten unerwartet fließen.
Option B ist nicht falsch. Amazon RDS, Google Cloud SQL, Azure Database for PostgreSQL, AWS ECS Fargate oder Cloud Run können Sicherheitsarbeit abnehmen. Sie nehmen dir aber nicht die Entscheidung ab, ob Logs in eine andere Region gehen, ob Backups verschlüsselt sind, wer IAM-Rollen besitzt und ob ein AV-Vertrag nötig ist. Der Preis ist auch nicht nur Geld: Du brauchst Zeit für IAM Least Privilege, KMS-Schlüssel, VPC-Regeln, Audit-Trails und Löschkonzepte.
Eine veröffentlichte technische Grenze, die oft übersehen wird: HTTP Strict Transport Security nach RFC 6797 wirkt erst sauber, wenn max-age lang genug und HTTPS überall korrekt ist. Für ein vorsichtiges Rollout ist max-age=300 ein Testwert, nicht das Ziel; für stabile Produktion sind 31536000 Sekunden üblich, aber erst sinnvoll, wenn Subdomains, Zertifikate und Weiterleitungen stimmen. Wer HSTS falsch setzt, sperrt Nutzer nicht selten härter aus, als es ein alter HTTP-Endpunkt getan hätte.
Bei APIs gilt dasselbe. OAuth 2.1, OpenID Connect Core 1.0, JWT nach RFC 7519, TLS 1.3 nach RFC 8446 und SameSite-Cookies sind nützlich, aber sie ersetzen keine Datenminimierung. Ein JWT mit zu vielen Claims ist aus Privacy-Sicht schlechter als eine serverseitige Session, weil das Token häufig in Browsern, Proxies, Logs und Support-Tickets auftaucht. Ich würde JWTs für ein kleines, serverseitig gerendertes Java-System nicht einführen, weil der Nutzen gering ist und das Risiko von Token-Leaks steigt.
Ein praktisches Kriterium: Wenn du keine regelmäßige Rotation von Secrets nachweisen kannst, solltest du erst keinen Secret-Sprawl erzeugen. Docker Compose mit einer lokalen .env-Datei ist nicht elegant, aber verständlich. HashiCorp Vault 1.15 oder 1.16 ist stark, doch ohne Betriebskonzept kann Vault selbst zum Single Point of Failure werden. SOPS mit age-Verschlüsselung ist für Solo-Freelancer oft der bessere Mittelweg, weil verschlüsselte Dateien im Git-Workflow bleiben und keine Serverkomponente betrieben werden muss.
Die Warnsignale sind real, aber ihre Priorität ist bei kleinen Budgets anders
Den Beitrag Legacy Java modernisieren: Warnsignale und Strategie würde ich trotzdem nicht als starre Checkliste übernehmen, weil dieselben Warnsignale bei einem Solo-Projekt andere Folgekosten haben als in einem Team mit Security Engineering. Ein veraltetes Framework ist kritisch, aber ein unkontrollierter Modernisierungsplan ist ebenfalls kritisch, weil du damit aus einem bekannten Risiko mehrere unbekannte Risiken machst.
Die wichtigste Frage lautet nicht: „Wie schnell komme ich auf Java 21?“ Sie lautet: „Welche Daten verlassen nach der Änderung zum ersten Mal den bisherigen Vertrauensbereich?“ Diese Frage ist konkreter, weil sie Build-Tools, Scanner, Hosting, Monitoring und Supportzugriffe einschließt. GitHub Actions, GitLab CI, Dependabot, Renovate, SonarQube, Snyk, Semgrep und CodeQL sind hilfreich, aber sie verarbeiten Quellcode, Metadaten oder Findings; je nach Vertrag kann bereits das relevant sein.
Ein weiterer Richtwert aus der Praxis: Plane für die erste Sicherheitsinventur eines kleinen Legacy-Java-Systems 6 bis 10 Stunden, nicht einen Nachmittag. Das ist eine Erfahrungsgröße, weil du Abhängigkeiten, Datenklassen, Logpfade, Deployments, Backups und externe Dienste erfassen musst. Wer diese Zeit nicht anbietet, kalkuliert das Risiko nur unsichtbar in spätere Fehler ein.
Ich würde auch keine automatische „Update-all“-Strategie mit Renovate oder Dependabot aktivieren, ohne vorher Tests und Rollback zu klären, weil Sicherheitsupdates in Java oft transitive Versionen verändern und dadurch Serialisierung, JDBC-Treiber, Hibernate-Mappings oder TLS-Defaults brechen. Besser ist eine feste Update-Kadenz, zum Beispiel alle 2 Wochen für unkritische Updates und sofort für aktiv ausgenutzte CVEs; diese Zahl ist ein Arbeitsrhythmus, kein Compliance-Gesetz.
Besonders tückisch sind Datenbanktreiber und ORM-Schichten. Ein Wechsel von MySQL Connector/J 5.1 auf 8.0, von PostgreSQL JDBC 42.2 auf 42.7 oder von Hibernate 5 auf 6 kann TLS-Verhalten, Zeitzonen, Prepared Statements und Lazy Loading verändern. Das ist sicherheitsrelevant, weil Fehlkonfigurationen entweder Daten zu offen übertragen oder durch neue Fehlermeldungen mehr interne Details preisgeben. Deshalb sollte jeder Treiberwechsel eine Mini-Datenschutzprüfung bekommen: Welche Verbindungsparameter ändern sich, welche Fehlermeldungen entstehen, welche Logs werden geschrieben?
Auch Passwörter sind kein Nebenschauplatz. Wenn ein altes System MD5, SHA-1 oder selbst gesalzene Hashes nutzt, ist Modernisierung dringend, weil moderne Hardware schwache Hashes schnell prüft. Für neue Hashes sind Argon2id, bcrypt oder PBKDF2 nach OWASP Password Storage Cheat Sheet geeignet. Ein vertretbarer bcrypt-Kostenfaktor kann 12 sein, wenn Login-Latenz auf deinem Zielserver akzeptabel bleibt; dieser Wert ist zu messen, weil ein billiger VPS sonst bei mehreren Logins spürbar langsam wird.
Für Datenschutz lohnt sich außerdem eine kleine Metrik, die kaum jemand erhebt: Anzahl der Orte, an denen personenbezogene Daten gespeichert werden. Wenn aus Datenbank plus Backup plötzlich Datenbank, Backup, CI-Artefakt, Log-Aggregator, Error-Tracker und lokaler Docker-Dump werden, hat sich die Löschpflicht vervielfacht. Diese Behauptung ist nicht alarmistisch, weil DSGVO-Rechte wie Auskunft und Löschung praktisch nur erfüllbar sind, wenn Speicherorte bekannt sind.
Der erste Schritt ist ein Daten- und Beweis-Stopp
Beginne nicht mit Framework-Auswahl, sondern mit einer einstündigen Sperrliste: Welche Daten dürfen ab heute nicht mehr in Logs, Tickets, Dumps, Screenshots, CI-Artefakte und externe Scanner? Danach prüfst du Abhängigkeiten und Secrets lokal mit einem reproduzierbaren Skript. Erst wenn klar ist, welche Beweise du sicher erzeugen kannst, lohnt sich die eigentliche Legacy-Java-Modernisierung.



