1. Konfiguration nach dem Jar-Build
Wenn eine Anwendung in der IDE lebt, gewöhnt sich das Gehirn schnell an die Illusion, dass „Projekt = Anwendung“ ist. In der IDE liegt der Ordner src/main/resources direkt daneben, Profile werden per Häkchen in der Run Configuration aktiviert, und die benötigten Parameter können versehentlich in den Starteinstellungen gespeichert sein. Aber die jar-Datei ist bereits ein „Koffer mit Sachen“: Du hast sie weggegeben – und dann hast du kein Recht mehr, heimlich noch eine Socke hineinzulegen.
Und dann stellt sich plötzlich heraus, dass die Konfiguration kein dekoratives YAML „für die Optik“ ist, sondern ein Teil des Startvertrags. Für einen Boot-Service ist das normale Modell: ein und dasselbe jar wird in verschiedenen Umgebungen gestartet (local/dev/prod), und die Unterschiede werden durch Profile, Umgebungsvariablen, Kommandozeilenargumente und externe Konfigurationen festgelegt. Wenn du das kannst – dann beherrschst du Spring Boot wirklich. Wenn nicht – dann kannst du nur auf den grünen Pfeil klicken (auch eine Fähigkeit, nur … etwas weniger monetarisierbar).
In dieser Vorlesung gehen wir davon aus, dass das jar bereits gebaut ist und z. B. hier liegt:
build/libs/catalog-service-0.0.1-SNAPSHOT.jar
Wenn dein Dateiname anders ist – das ist normal; Hauptsache, du startest dieselbe Datei, die du gerade gebaut hast.
2. Woher bezieht Spring Boot beim Start eines jar die Eigenschaften
Bevor wir Profile und Eigenschaften überschreiben, ist es nützlich, Ordnung im Kopf zu schaffen: Boot sammelt zunächst eine Reihe von Eigenschaftsquellen (property sources) und wendet dann die Prioritätsregel an. Dieses Modell haben wir bereits im Modul zur externalized configuration besprochen, aber jetzt wird es besonders praktisch: Du drehst buchstäblich an den Stellschrauben, und die Anwendung gehorcht entweder oder nicht.
Unten siehst du eine vereinfachte Karte der Quellen, ohne den Versuch, die Vorlesung in ein Nachschlagewerk zu verwandeln. Wichtig ist, das Prinzip zu verstehen: In diesem Schema steigt die Priorität von oben nach unten. Was weiter unten steht, überschreibt (override) das, was weiter oben gesetzt wurde. SPRING_APPLICATION_JSON nehmen wir vorerst nicht ins Diagramm auf, weil wir es weiter unten separat besprechen.
flowchart TB
A["Niedrige Priorität: innerhalb des jar – application.yaml und application-{profile}.yaml"] --> B["Externe config files: ./config, zusätzliche location/import"]
B --> C["Environment variables"]
C --> D["Java system properties -D…"]
D --> E["Hohe Priorität: command-line args --key=value"]
E --> F["Endgültige Konfiguration in Environment"]
Die häufigste Überraschung für Anfänger beim packaged-run: Sie ändern application.yaml im Projekt und erwarten, dass dies das bereits gebaute jar beeinflusst. Aber das jar ist ein „Passfoto“: Es ändert sich nicht, nur weil du dir die Haare schneiden lässt. Wenn du Ressourcen bearbeitest – musst du das jar neu bauen. Wenn du das Verhalten ohne Neubau ändern möchtest – verwende Runtime-Overrides (env vars, -D, --…, externe Konfigurationen).
Für den catalog-service (gemäß Projektspezifikation) sind die Basiskonfigurationen normalerweise:
application.yaml – allgemein.
application-local.yaml, application-dev.yaml, application-prod.yaml – profilbezogen.
catalog-data.yaml – eine importierte Datei mit der Kursliste (via spring.config.import).
Und all das kann innerhalb des jar leben; darüber hinaus kannst du externe Dateien haben (z. B. ./config/catalog-extra.yaml) oder sogar einen separaten Konfigurationsordner, der auf dem Server neben dem jar liegt.
3. Profil beim java -jar aktivieren: CLI args, env vars und -D
Ein Profil ist nicht nur ein „Anwendungsmodus für die Optik“, sondern eine Möglichkeit, die gewünschte Konfiguration und (manchmal) die gewünschten Beans zu aktivieren. In unserem Kurs sind Profile eines der Schlüsselwerkzeuge: local für bequeme Entwicklung, dev für eine „realistischere“ Diagnose, prod für minimale, aber sichere Exponierung und strengere Einstellungen.
Technisch gesehen kann man ein Profil auf verschiedenen Wegen aktivieren. Der Unterschied liegt nicht in der Magie von Spring, sondern darin, wie genau du den Parameter an den Prozess übergibst. Und hier ist es nützlich, so zu denken: „Ich brauche ein aktives Profil – ich wähle den Lieferkanal.“
Der direkteste und anschaulichste Weg ist das Kommandozeilenargument:
java -jar build/libs/catalog-service-0.0.1-SNAPSHOT.jar \
--spring.profiles.active=dev
Wenn du Umgebungsvariablen magst (und sie passen normalerweise gut in den Start von Diensten), dann sieht es so aus:
SPRING_PROFILES_ACTIVE=prod \
java -jar build/libs/catalog-service-0.0.1-SNAPSHOT.jar
Die dritte Variante sind Java-System-Properties über -D. Das ist etwas „oldschool“, aber immer noch ein funktionierender und verbreiteter Weg:
java -Dspring.profiles.active=local \
-jar build/libs/catalog-service-0.0.1-SNAPSHOT.jar
Was wählen? Im Studienprojekt – irgendeines, solange du den Start ohne IDE reproduzieren kannst. Im echten Leben gewinnen oft env vars (sie harmonieren gut mit Prozessmanagern) oder CLI args (sie sind maximal explizit). Hauptsache, du hältst das Profil nicht „nur in der IDE“, denn dann wird der jar-Start zur Lotterie.
4. Anwendungsspezifische Eigenschaften beim jar-Start überschreiben: --, env vars und -D
Das Profil ist nur der Anfang. Die wahre Stärke der externalized configuration liegt darin, dass du das Verhalten der Anwendung punktuell ändern kannst: Port, Verhaltensflags, Grenzen, Diagnoseeinstellungen. Und das alles ohne Neubau des jar, allein durch Startparameter.
CLI args: direkt im Start überschreiben
CLI args in Boot sehen aus wie --key=value. Für den jar-Start ist das besonders praktisch, weil du immer eine einzige Zeile hast, die du kopieren, in der README speichern oder an einen Kollegen weitergeben kannst.
Starten wir zum Beispiel den catalog-service im Profil dev und auf einem anderen Port:
java -jar build/libs/catalog-service-0.0.1-SNAPSHOT.jar \
--spring.profiles.active=dev \
--server.port=9090
Fügen wir nun die Überschreibung einer anwendungsspezifischen Eigenschaft hinzu. Laut Projektspezifikation haben wir den Namespace app.catalog.*, und nehmen wir an, wir möchten das Limit der featured-Kurse erhöhen:
java -jar build/libs/catalog-service-0.0.1-SNAPSHOT.jar \
--spring.profiles.active=dev \
--app.catalog.max-featured-count=6
Wenn du im Code ein Verhalten hast, das an den maintenance-mode gebunden ist, kannst du es genauso einschalten:
java -jar build/libs/catalog-service-0.0.1-SNAPSHOT.jar \
--spring.profiles.active=local \
--app.catalog.maintenance-mode=true
Beachte eine kleine, aber wichtige Sache: Wir übergeben die Eigenschaften in kanonischer Form, also derselben wie in YAML. Boot kümmert sich dann selbst um naming conventions und Binding.
Env vars: Überschreiben über die Umgebung
Umgebungsvariablen sind eine tolle Möglichkeit, wenn die Startzeile nicht aufgebläht werden soll oder wenn du den Dienst über einen Manager startest, der bequem env vars setzt.
Für server.port sieht das so aus:
SPRING_PROFILES_ACTIVE=dev \
SERVER_PORT=9090 \
java -jar build/libs/catalog-service-0.0.1-SNAPSHOT.jar
Und für app.catalog.max-featured-count ist es wichtig, sofort eine kanonische Form zu verwenden, um keine konkurrierenden Namensvarianten zu erzeugen. In env vars stützt sich Boot auf den kanonischen Eigenschaftsnamen: Punkte werden zu _, Bindestriche aus dem kebab-case werden entfernt, alles wird in Großbuchstaben umgewandelt. Daher wird app.catalog.max-featured-count zu APP_CATALOG_MAXFEATUREDCOUNT.
SPRING_PROFILES_ACTIVE=dev \
APP_CATALOG_MAXFEATUREDCOUNT=6 \
java -jar build/libs/catalog-service-0.0.1-SNAPSHOT.jar
Eine kleine Tabelle als Spickzettel (speziell für unsere Projekteigenschaften):
| Kanonische Eigenschaft (YAML/CLI) | Beispiel env var |
|---|---|
| spring.profiles.active | SPRING_PROFILES_ACTIVE |
| server.port | SERVER_PORT |
| app.catalog.title | APP_CATALOG_TITLE |
| app.catalog.max-featured-count | APP_CATALOG_MAXFEATUREDCOUNT |
| app.catalog.maintenance-mode | APP_CATALOG_MAINTENANCEMODE |
System properties -D: Überschreiben in der JVM
System properties sind nützlich, wenn du den JVM-Start über eine einzelne „JVM-Zeile“ steuerst. Zum Beispiel:
# In dieser Variante werden alle Overrides als JVM-Eigenschaften übergeben
java \
-Dspring.profiles.active=prod \
-Dserver.port=8085 \
-Dapp.catalog.max-featured-count=4 \
-jar build/libs/catalog-service-0.0.1-SNAPSHOT.jar
Das ist weder besser noch schlechter als env vars oder CLI args – es ist einfach ein anderer Lieferkanal. Aber es ist nützlich, sich daran zu erinnern: Du wirst -D… in echten Projekten ziemlich oft sehen.
5. SPRING_APPLICATION_JSON
Manchmal möchte man nicht eine einzelne Eigenschaft übergeben, sondern gleich einen ganzen Satz, und dafür zehn env vars zu verwenden – ist mühsam. In solchen Momenten bietet Spring Boot den Mechanismus SPRING_APPLICATION_JSON: Du übergibst einen JSON-String, der ein „Stück Konfiguration“ enthält, und Boot behandelt dies als eine weitere Eigenschaftsquelle.
Das ist vergleichbar mit der Situation „Hier ist dein mini-application.yaml, nur in JSON-Form“. Beispiel (Achtung: JSON-Anführungszeichen sind deine neue Quelle für Abenteuer, besonders in verschiedenen Shells):
# Eine einzige env var, die ein „Konfigurationsstück“ enthält
# Wichtig: Anführungszeichen/Escaping hängen von deiner Shell ab (bash/zsh/powershell usw.)
SPRING_APPLICATION_JSON='{
"spring": { "profiles": { "active": "dev" } },
"server": { "port": 9090 },
"app": { "catalog": { "max-featured-count": 6 } }
}' \
java -jar build/libs/catalog-service-0.0.1-SNAPSHOT.jar
Der Sinn hier ist einfach: Mit einer einzigen Umgebungsvariable haben wir das Profil aktiviert, den Port geändert und das Limit der featured-Kurse überschrieben.
Warum ist dieser Mechanismus gerade im Kontext des packaged-run nützlich? Weil du anfängst, wie ein Ingenieur zu denken, der die Anwendung zwischen Umgebungen „transportiert“: jar ist eins, die Einstellungen ändern sich von außen. SPRING_APPLICATION_JSON ist eine Möglichkeit, einen Teil der Einstellungen „neben den Start“ zu legen, ohne eine separate Datei zu erstellen.
Und ja, falls du jetzt denkst: „Klingt cool, aber ich werde bestimmt irgendwo ein Anführungszeichen vergessen“ – das ist ein völlig normales Gefühl. Selbst erfahrene Entwickler erwischen sich manchmal bei solchen Fehlern, weil JSON in einer Umgebungsvariable ist wie IKEA ohne Anleitung aufzubauen, aber mit Selbstvertrauen.
6. Externe Konfigurationen: additional-location und location
Bis jetzt haben wir das Verhalten über Startparameter (env vars, -D, CLI args) geändert. Aber es gibt oft ein noch „betreiberorientierteres“ Szenario: Die Konfiguration sollte in einer separaten Datei leben, neben dem jar oder in einem eigenen Verzeichnis, damit man sie versionieren, ändern und auf verschiedenen Maschinen bereitstellen kann, ohne den Build anzufassen.
In unserem Projekt ist laut Spezifikation sogar ein Ordner ./config/ und eine optionale externe Datei ./config/catalog-extra.yaml vorgesehen. Nun die Frage: Wie sagst du Boot „schau da auch noch rein“?
spring.config.additional-location: Erweitere die Standardsuche vorsichtig
spring.config.additional-location fügt eine weitere Location zur Standardsuche hinzu. Das bedeutet: Boot liest wie gewohnt die Konfigurationen aus dem jar und den standardmäßigen externen Orten, und dann zusätzlich schaut es in die angegebene Location.
java -jar build/libs/catalog-service-0.0.1-SNAPSHOT.jar \
--spring.profiles.active=dev \
--spring.config.additional-location=optional:file:./config/
Hier sind drei Details wichtig.
Erstens: file: – das ist ein expliziter Hinweis, dass wir aus dem Dateisystem lesen, nicht aus dem Classpath.
Zweitens: ./config/ – ein relativer Pfad. Er wird relativ zum aktuellen Arbeitsverzeichnis des Prozesses aufgelöst, nicht relativ zum Speicherort des jar. Das werden wir weiter unten noch separat besprechen, weil es einer der häufigsten „Warum funktioniert es nicht?!“-Punkte ist.
Drittens: optional: bedeutet „Wenn der Ordner/die Datei nicht existiert, stürze nicht ab“. Das ist nützlich, wenn die externe Konfiguration nicht obligatorisch ist. Im Studienprojekt ist das oft praktisch: Man kann sowohl ohne ./config starten als auch mit.
Wie sucht Boot nach Dateien in diesem Verzeichnis? Normalerweise sucht es nach Standardnamen wie application.yaml, application-dev.yaml usw. Das heißt, wenn du eine Datei application-dev.yaml in ./config/ ablegst, kann sie das überschreiben, was im jar liegt, ohne dass ein Neubau erforderlich ist.
spring.config.location: Ersetze die Standardsuche vollständig
spring.config.location ist ein anderes Werkzeug. Es fügt nicht „noch einen Ort hinzu“, sondern ersetzt die Standardsuche nach Konfiguration durch das, was du angibst. Das ist eine starke und manchmal gefährliche Aktion: Man kann versehentlich das Laden der internen Ressourcen „ausschalten“, an die man gewöhnt ist.
java -jar build/libs/catalog-service-0.0.1-SNAPSHOT.jar \
--spring.config.location=file:./config/
Wann ist das angebracht? Wenn du strikt sagen willst: „Ein Start ohne externe Konfiguration ist verboten.“ Das kommt in echten Systemen vor, wo die Konfiguration separat kommen muss (z. B. zusammen mit Secrets) und ein Start mit „Defaults“ als Fehler betrachtet wird.
Aber im Studienprojekt und bei den meisten ersten Diensten würde ich mit additional-location beginnen, weil es sanfter und einfacher fürs Debugging ist: Du fügst eine externe Datei hinzu – siehst den Effekt; nimmst sie weg – kehrst zur Konfiguration im jar zurück.
7. Überprüfen, ob Profil und Überschreibungen angewendet wurden
Wenn die Konfiguration „von außen“ kommt, besteht immer die Gefahr der Selbsttäuschung: Du bist sicher, du hast den richtigen Parameter übergeben, aber die Anwendung lebt weiterhin nach den alten Werten. Und hier ist es wichtig, nicht zu raten, sondern zu überprüfen. Glücklicherweise haben wir für den catalog-service bereits eine gute diagnostische Basis geschaffen: Logs und Actuator.
Die einfachste und „ärmliche, aber ehrliche“ Methode ist, wichtige Parameter beim Start zu protokollieren. Genau dafür gibt es in unserem Projekt den StartupSummaryRunner: Er soll melden, mit welchen Profilen und welchen Schlüsseleinstellungen der Dienst gestartet ist. Das ist besonders im jar-Modus wichtig, weil die IDE dann nicht mehr das aktive Profil mit einem hübschen Häkchen hervorhebt.
Ein kleines Beispiel, wie ein Ausschnitt des Startlogs aussehen könnte (Idee, nicht das einzig richtige Format):
Aktive Profile: dev
Serverport: 9090
Katalogtitel: Spring+ Catalog
Max featured count: 6
Wenn du dieses Log robuster machen und an unsere typisierte Konfiguration anbinden möchtest, hier ein kompaktes Beispiel (nicht die „vollständige Klassenversion“, sondern ein Ausschnitt, der den Gedanken zeigt). Achte auf die Imports: Ich schreibe sie bewusst explizit, damit du siehst, woher die Typen kommen.
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.core.env.Environment;
import org.springframework.stereotype.Component;
@Component // Registriert die Klasse als Bean, damit sie beim Start des Kontexts erstellt wird
public class StartupConfigLog {
private static final Logger log = LoggerFactory.getLogger(StartupConfigLog.class);
public StartupConfigLog(CatalogProperties props, Environment env) {
// Environment – die „finale“ Schicht, in der alle Eigenschaftsquellen mit Prioritäten sichtbar sind
log.info("Active profiles: {}", String.join(",", env.getActiveProfiles()));
// Typisierte Konfiguration (CatalogProperties) zeigt, dass das Binding wirklich funktioniert hat
log.info("Max featured count: {}", props.maxFeaturedCount()); // z. B.: 6
}
}
Ja, dieses Beispiel ist ein kleines „Cheating“: Es loggt im Konstruktor. In echtem Code würdest du das über ApplicationRunner machen, um nach dem Hochfahren des Kontexts an einem vorhersagbaren Punkt des Lebenszyklus zu loggen (das haben wir früher gemacht). Aber als Illustration der Verbindung von Environment + CatalogProperties + Log funktioniert es gut und braucht genau so viele Zeilen, um nicht unterzugehen.
Die zweite Methode ist Actuator. Wenn in deinem local/dev-Profil /actuator/env und /actuator/configprops geöffnet sind, kannst du den tatsächlichen Wert direkt aus dem laufenden Prozess überprüfen. Zum Beispiel (abstrakte Anfrage, das Format hängt davon ab, wie du den Endpunkt aufrufst – über Browser, curl oder HTTP-Datei):
GET http://localhost:9090/actuator/env
GET http://localhost:9090/actuator/configprops
Der Sinn dieser Überprüfungen ist einfach: Wenn du --app.catalog.max-featured-count=6 übergeben hast, dann solltest du sehen, dass der finale Wert im Environment tatsächlich 6 ist und dass CatalogProperties ebenfalls mit diesem Wert verknüpft ist.
8. Relative Pfade und das Arbeitsverzeichnis des Prozesses
Es gibt eine hinterhältige Sache, die den Start externer Konfigurationen öfter zerstört als Spring, Gradle und kosmische Strahlung zusammen. Das ist das aktuelle Arbeitsverzeichnis des Prozesses (working directory). Wenn du file:./config/ schreibst, sagst du eigentlich: „Nimm den Ordner config relativ zu dem Ort, von dem aus ich den Befehl gestartet habe.“
Und der Ort, von dem aus du den Befehl startest, kann unterschiedlich sein. Zum Beispiel kannst du:
- im Wurzelverzeichnis des Projekts sein und java -jar build/libs/… ausführen;
- innerhalb von build/libs sein und java -jar catalog-service-… ausführen;
- das jar aus einem ganz anderen Ordner starten, in den du es kopiert hast.
Und in allen drei Fällen ist ./config/ ein anderer Pfad.
Ein Beispiel, das „sinngemäß“ gleich aussieht, sich aber im working directory unterscheidet:
# Variante 1: Start vom Projektstamm aus
java -jar build/libs/catalog-service-0.0.1-SNAPSHOT.jar \
--spring.config.additional-location=optional:file:./config/
# Variante 2: Zuerst in build/libs gewechselt, und jetzt sucht ./config dort
cd build/libs
java -jar catalog-service-0.0.1-SNAPSHOT.jar \
--spring.config.additional-location=optional:file:./config/
In der zweiten Variante sucht Boot nach build/libs/config, nicht nach <Projektstamm>/config. Wenn du dir darüber keine Gedanken gemacht hast, wirst du sicher sein, dass „Spring Boot kaputt ist“. Aber es macht nur ehrlich, was du geschrieben hast.
Wie behebt man das? Der einfachste Ansatz: Starte den Prozess aus einem „stabilen“ Verzeichnis (dort, wo du die Konfiguration erwartest), oder gib den Pfad explizit an (z. B. absolut), oder halte die externe Konfiguration neben dem jar und starte aus demselben Ordner.
9. Typische Fehler beim jar-Start
Fehler Nr. 1: jar ohne Profil starten und sich wundern, warum „in dev war es anders“.
Wenn du die Anwendung aus der IDE gestartet hast, konnte das Profil durch die IDE-Einstellung oder eine Umgebungsvariable aktiviert sein, die die IDE bereitstellt. Beim jar-Start gibt es diesen „Gedankenlese-Dienst“ nicht mehr. Du startest also ohne Profil und bekommst das Standardverhalten. Die Lösung ist einfach: Das Profil muss Teil des Startbefehls oder der Umgebung sein, nicht Teil des IDE-Speichers.
Fehler Nr. 2: application.yaml im Projekt ändern und das jar nicht neu bauen.
Das ist ein Klassiker. Du bearbeitest Ressourcen, startest das alte jar und denkst, die Änderungen „funktionieren nicht“. Aber das jar ist eine separate Datei, die nicht auf deinen src-Ordner schaut. Wenn du möchtest, dass Änderungen in den Ressourcen in das Artefakt gelangen, musst du ein neues jar bauen. Wenn du das Verhalten ohne Build ändern möchtest, verwende Runtime-Overrides oder externe Konfiguration.
Fehler Nr. 3: spring.config.additional-location und spring.config.location verwechseln.
additional-location erweitert die Suche und „vergibt“ normalerweise das Fehlen externer Dateien, besonders mit optional:. location ersetzt die Standardsuche und kann plötzlich einen Teil des gewohnten Konfigurationsverhaltens deaktivieren. Wenn du dir nicht sicher bist, dass du ein striktes Verbot des Starts ohne externe Konfiguration brauchst – beginne mit additional-location.
Fehler Nr. 4: Die env var für eine anwendungsspezifische Eigenschaft falsch benennen.
Wenn du von --app.catalog.max-featured-count=6 zu env vars wechselst, kann man leicht den Namen falsch schreiben und das Gefühl bekommen „Die Variable ist da, aber die Eigenschaft wurde nicht angewendet“. Hilfreich ist Disziplin: Halte dich an eine kanonische Form wie APP_CATALOG_MAXFEATUREDCOUNT und überprüfe dann den tatsächlichen Wert über Startlogs oder Actuator (env/configprops).
Fehler Nr. 5: Das aktuelle Arbeitsverzeichnis des Prozesses nicht berücksichtigen und die externe Konfiguration verlieren.
file:./config/ ist nicht „neben dem jar“, sondern „neben dem working directory“. Wenn du das jar aus einem anderen Ordner startest, ändert der relative Pfad seine Bedeutung. Das ist kein Bug von Spring Boot und kein Grund „dann härte ich den Pfad im Code“ (bitte nicht). Das ist ein Grund, den Start reproduzierbar und bewusst zu gestalten: Entweder das Startverzeichnis festlegen oder den Pfad explizit angeben.
GO TO FULL VERSION