Zum Inhalt springen

Java

Starte im Assistenten mit einer Java-Vorlage oder wähl Java-Anwendung für ein leeres Projekt oder ein Repository, oder nimm Java im Bereich Laufzeit zu einem bestehenden Projekt dazu. Der Container envoryx-<projekt>-java läuft mit ghcr.io/envoryx/envoryx-java:<version>. Das ist Eclipse Temurin (<version>-jdk-noble) plus Maven, Gradle, git und socat. Er läuft als PUID:PGID, mit deinem Projekt unter /var/www/html und einem Home-Verzeichnis unter /home/envoryx.

Das Repository von Maven liegt im Paket-Cache des Projekts; teilst du den Cache, wird eine Abhängigkeit für alle Projekte nur einmal geladen. Gradle hält sein Home (Abhängigkeiten, Wrapper-Distributionen) in ~/.gradle im Projekt-Home, auch bei geteiltem Paket-Cache: Gradle reicht seine Cache-Sperren über eine Nachricht an localhost weiter, die keinen anderen Container erreicht, und mit geteiltem Gradle-Home wartet Gradle in einem Projekt auf das eines anderen, bis es aufgibt.

Nur die LTS-Releases: Standard ist 25, außerdem gibt es 21 und 17.

Steht in pom.xml oder build.gradle(.kts) ein höheres Java-Level als die Version, mit der das Projekt läuft, warnen Projektliste und Projektseite und nennen beide Versionen. Das Spring-Boot-Template zielt zum Beispiel auf Java 25; stellst du so ein Projekt auf 21 um, bekommst du diese Warnung statt später eines UnsupportedClassVersionError. Envoryx liest die Dateien, es wertet sie nicht aus: Ein Level aus einer Variable oder einer Parent-POM ergibt keine Warnung.

Hast du das Level danach passend gesenkt, wartet noch eine Falle: Die Klassen in target/ oder build/ hat das neuere JDK kompiliert, und weil sich die Quellen nicht geändert haben, kompilieren weder Maven noch die Dev-Modi sie neu. Deshalb schaut Envoryx vor dem Server und vor einem Testlauf auf die kompilierten Klassen, und sind sie für ein neueres Java als das im Container, löscht es target/classes, target/test-classes oder build/classes, mit einer Zeile im Log. Der nächste Build kompiliert dann alles für die richtige Version.

Das entscheidest nicht du, sondern das Projekt. Eine pom.xml heißt Maven, sonst heißt eine build.gradle oder build.gradle.kts Gradle. Bringt das Projekt seinen Wrapper mit (mvnw, gradlew), gewinnt der Wrapper gegen Maven oder Gradle aus dem Image. Er läuft über sh, deshalb funktioniert auch ein Wrapper, der in einem Zip oder einem Windows-Checkout sein Ausführungsrecht verloren hat.

Schalte Java-Server ausführen ein und wähl eine Vorlage. Der Server wird zum Hauptprozess des Containers, startet automatisch neu und bekommt einen eigenen Host-Port. Bevor er startet, wartet er auf die Build-Datei. Schaltest du den Server aus und wieder ein, behält er diesen Port, ein Lesezeichen darauf bleibt also gültig.

Die Karte Java im Bereich Laufzeit mit der Version, dem eingeschalteten Spring-Boot-Server, dem Modus, dem Port und der Option für JDWPDie Karte Java im Bereich Laufzeit mit der Version, dem eingeschalteten Spring-Boot-Server, dem Modus, dem Port und der Option für JDWP
Vorlage Entwicklung Produktion Port
Spring Boot mvn spring-boot:run oder gradle bootRun einmal bauen, dann das Jar starten 8080
Quarkus mvn quarkus:dev oder gradle quarkusDev einmal bauen, dann quarkus-run.jar 8080
Andere (bauen, dann java -jar) - einmal bauen, dann das Jar starten 8080

In der Entwicklung kompiliert Quarkus bei der nächsten Anfrage nach einer Änderung neu. Die DevTools von Spring Boot starten die Anwendung neu, wenn sich kompilierte Klassen ändern. Nach dem Bearbeiten führst du also mvn compile im Java-Terminal aus oder lässt deine IDE bauen. Eine neue Abhängigkeit ist etwas anderes: Die DevTools starten immer mit dem Klassenpfad neu, mit dem sie gestartet sind. Hast du etwas in die pom.xml oder build.gradle aufgenommen, startest du das Projekt neu.

Der Produktionsmodus baut das Projekt einmal ohne die Tests (mvn package, gradle build) und startet dann das Jar: bei Quarkus target/quarkus-app/quarkus-run.jar, sonst das neueste Jar in target/ oder build/libs/, ohne die -plain-, -sources-, -javadoc- und -tests-Jars. Die dritte Vorlage für Micronaut, Javalin, Helidon und Co. hat keinen Entwicklungsmodus. Sie baut und startet das Jar, das du angibst (etwa build/libs/app.jar), oder das neueste.

Der Port steht in SERVER_PORT, QUARKUS_HTTP_PORT, MICRONAUT_SERVER_PORT und PORT, und die passenden Host-Variablen stehen auf 0.0.0.0. Die gängigen Frameworks lauschen damit ohne weitere Konfiguration dort, wo der Proxy sie erreicht.

Für PostgreSQL, MySQL und MariaDB setzt Envoryx SPRING_DATASOURCE_URL, _USERNAME und _PASSWORD für Spring Boot und QUARKUS_DATASOURCE_DB_KIND, _JDBC_URL, _USERNAME und _PASSWORD für Quarkus, dazu eine einfache JDBC_URL für alles andere. Eine zusätzliche Datenbank namens analytics kommt als ANALYTICS_JDBC_URL an. Auch MongoDB, Redis und Mailpit bekommen ihre Spring- und Quarkus-Variablen (SPRING_DATA_MONGODB_URI, SPRING_DATA_REDIS_URL, QUARKUS_REDIS_HOSTS, SPRING_MAIL_HOST, QUARKUS_MAILER_HOST und so weiter).

Die Dev Services von Quarkus sind abgeschaltet (QUARKUS_DEVSERVICES_ENABLED=false). Deine Anwendung nutzt die Datenbank des Projekts und keinen Container, den Quarkus selbst starten würde.

Ohne PHP und ohne Python-, Go- oder Ruby-Server ist der Java-Server die Anwendung: https://<projekt>.<basis> und jede weitere Domain gehen direkt zu ihm, und Direktzugriff ist der Java-Host-Port. Neben einem der anderen hat er nur seinen eigenen Host-Port.

Eine JVM legt ihren Heap beim Start fest: Mit einem Speicherlimit am Projekt ein Viertel davon, ohne Limit ein Viertel des Host-Speichers. Daraus folgen zwei Dinge. Gib einem Java-Projekt Platz, etwa 2 GiB: In der Entwicklung laufen Build-Tool und Anwendung jeweils in einer eigenen JVM, Gradle legt noch seinen Daemon dazu (einen zweiten, wenn du die Tests neben bootRun laufen lässt), und mit 1 GiB reißt ein Testlauf den Dev-Server mit. Und änderst du das Limit eines laufenden Projekts, setzt Envoryx es sofort um, die JVMs behalten aber den Heap, mit dem sie gestartet sind. Ein niedrigeres Limit endet dann damit, dass der Kernel den Prozess beendet, statt mit einem OutOfMemoryError von Java. Starte das Projekt nach einer Änderung am Limit also neu. Willst du den Heap selbst festlegen, erreicht JAVA_TOOL_OPTIONS=-XX:MaxRAMPercentage=50 im Bereich Umgebung jede JVM im Container.

Spring Boot kommt von start.spring.io mit Spring Web, Actuator und DevTools, dazu JPA und der Treiber, wenn das Projekt eine Datenbank hat. Quarkus REST kommt von code.quarkus.io mit REST (Jackson) und SmallRye Health, dazu Hibernate ORM mit Panache und der Treiber, wenn es eine Datenbank gibt. Beide tragen den Namen deines Projekts, laufen auf seiner Java-Version und werden einmal mit ihrem Wrapper gebaut. Build-Tool im Assistenten wählt Maven (pom.xml, mvnw) oder Gradle (Kotlin-DSL, build.gradle.kts, gradlew). Es steckt im zweiten Schritt unter Erweiterte Einstellungen, man übersieht es also leicht; ohne Auswahl bekommst du Maven. Über die API ist das templateBuildTool mit maven oder gradle. Der erste Build dauert ein paar Minuten, während die Abhängigkeiten geladen werden.

Hat das Projekt eine Datenbank, lassen beide Vorlagen Hibernate die Tabellen anlegen, denn keins der beiden Frameworks tut das gegen einen echten Datenbankserver von selbst: Die erste @Entity, die du hinzufügst, würde sonst mit relation does not exist enden. Spring Boot bekommt spring.jpa.hibernate.ddl-auto=update in application.properties. Sobald Flyway oder Liquibase das Schema verwalten, stellst du das auf validate oder none. Quarkus bekommt %dev.quarkus.hibernate-orm.schema-management.strategy=update, damit deine Daten das Live-Reload überstehen, und %test.quarkus.hibernate-orm.schema-management.strategy=drop-and-create, weil die Testdatenbank leer startet und ihre Tabellen bei jedem Lauf neu braucht. Beide Vorlagen bringen einen Pfad für den Health Check mit, den du gleich nutzen kannst: /actuator/health bei Spring Boot, /q/health bei Quarkus.

Die Startseite von Quarkus leitet im Entwicklungsmodus auf die Dev UI weiter, die von sich aus nur Anfragen für localhost beantwortet. Envoryx setzt QUARKUS_DEV_UI_HOSTS auf den Hostnamen des Projekts, ein Muster für die übrigen Namen unter der Basis-Domain und den öffentlichen Host, damit /q/dev-ui unter der Projekt-URL aufgeht. Das Muster braucht Quarkus 3.16 oder neuer; bei älteren Versionen klappt nur der eigene Hostname des Projekts. Eine zusätzliche Domain außerhalb der Basis-Domain trägst du im Bereich Umgebung in dieselbe Variable ein, durch Kommas getrennt. Der Produktionsmodus hat ohnehin keine Dev UI.

Der Bereich Tests führt mvn test oder gradle test aus (filtern mit -Dtest oder --tests) und zeigt das Ergebnis je Test aus den JUnit-Reports. Der Lauf richtet SPRING_DATASOURCE_URL, QUARKUS_DATASOURCE_JDBC_URL, JDBC_URL und DATABASE_URL auf <datenbank>_test, die Envoryx vorher anlegt. Bei einer URL mit Parametern wie ?useSSL=false kommt die Endung vor die Parameter, auch sie landet also bei der Testdatenbank. Ein Test mit create-drop kann deine Entwicklungsdatenbank deshalb nicht leeren.

Mit JDWP debuggen startet die JVM des Servers mit einem JDWP-Agent, auf Port 5005, solange du nichts anderes einstellst, und mit suspend=n, und veröffentlicht diesen Port auf einem Host-Port. In deinen Build musst du dafür nichts einbauen: Bei spring-boot:run landet der Agent in der abgespaltenen JVM der Anwendung, bei bootRun fügt ihn ein Init-Skript hinzu, und der Quarkus-Entwicklungsmodus bekommt -Ddebug.

In IntelliJ IDEA nimmst du Run → Remote JVM Debug mit Host und Port aus dem Bereich IDE. In VS Code verbindet sich das Extension Pack for Java mit einer launch.json wie dieser; der Bereich IDE zeigt sie mit deinem Host und Port:

.vscode/launch.json
{
"type": "java",
"name": "Attach to Envoryx",
"request": "attach",
"hostName": "<host>",
"port": <port>
}

Ohne Server wird nur der Port veröffentlicht - für eine JVM, die du selbst im Java-Terminal mit -agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=*:5005 startest.

Willst du gleich ganz im Container arbeiten, öffnet IntelliJ IDEA das Projekt über JetBrains Gateway mit JDK, Maven und Gradle aus dem Container, und VS Code macht dasselbe mit Remote-SSH. Der SSH-Benutzer ist <projekt>.java, wenn das Projekt mehr als eine Runtime hat, siehe IDE-Integration.

Im Bereich Aktionen gibt es java -version und je nach Build-Tool package, clean, dependency:tree und versions:display-dependency-updates für Maven oder build, clean, dependencies und tasks für Gradle.

Für Arbeit im Hintergrund gibt es zwei Worker-Vorlagen: Jar-Datei startet java -jar <datei>, etwa eine Spring-Boot-App mit Worker-Profil oder einen Queue-Consumer, und Build-Tool-Goal führt mvn <goal> oder gradle <task> aus, zum Beispiel exec:java -Dexec.mainClass=com.example.Worker. Cron-Jobs laufen im Java-Container wie in jedem anderen. Mehr dazu unter Worker und Cron-Jobs.

Die Java-Karte im Bereich Laufzeit hat einen Schalter Java aktivieren. Schaltest du ihn aus, verschwinden der Java-Container und die Container der Java-Worker; deine Dateien und die Worker-Definitionen bleiben. Auf der Kommandozeile kennt envoryx dafür --java <version>, --java-server und --java-preset spring-boot|quarkus|jar.