CodeGym /Kurse /Spring Test /@ConfigurationProperties in Tests

@ConfigurationProperties in Tests

Spring Test
Level 7, Lektion 3
Verfügbar

1. Konfiguration ist auch Code

Bisher haben wir besprochen, woher eine Eigenschaft überhaupt kommt: aus application-test.yml, lokalem Override oder Runtime-Registrierung. Sobald es aber mehr als ein paar Schlüssel sind, ändert sich die Fragestellung. Dann ist nicht nur wichtig „welcher Wert ist da“, sondern auch „in was wird es gebindet“, „welche Standardwerte hat es“ und „wann sollte die Konfiguration sofort fehlschlagen, anstatt später einen stillen Fehler zu verursachen“.

Wenn wir „Anwendungskonfiguration“ sagen, stellen wir uns leicht eine langweilige YAML-Datei vor, die irgendwo herumliegt und „irgendwie“ das Verhalten beeinflusst. In Wirklichkeit ist Konfiguration aber Code, nur nicht in Java geschrieben. Und dieser „Code“ hat eine unangenehme Eigenschaft: Der Compiler prüft ihn nicht, die IDE gibt keine Hinweise, und Tippfehler in Schlüsseln verhalten sich wie Ninjas – leise, plötzlich und im ungünstigsten Moment.

Das klassische Symptom: Sie haben mehrere Einstellungen für ein Subsystem, z. B. einen Moderation-Client. Sie beginnen mit ein paar @Value, fügen einen dritten hinzu, dann einen vierten. Und schon geistern Strings wie "contenthub.moderation.base-url", "contenthub.moderation.timeout", "contenthub.moderation.retries" durch den Code, und jemand hat versehentlich "contenthub.moderation.baseUrl" (ohne Bindestrich) geschrieben und wundert sich, warum der Wert nicht übernommen wird. Das Unangenehmste: Die Tests fangen auch an, diese Strings „nachzujustieren“, und nach ein paar Wochen sind Sie nicht mehr sicher, welcher Schlüssel wo gesetzt wird und warum ein bestimmter Test überhaupt funktioniert.

Um die Kontrolle zurückzugewinnen, wollen wir drei Dinge erreichen. Erstens: Die Einstellungen eines Subsystems sollen zusammengefasst sein, nicht über verschiedene Klassen verstreut. Zweitens: Die Werte sollen typisiert sein, denn ein Timeout ist kein String, sondern ein Duration, und ein Dateigrößenlimit ist nicht „5MB“, sondern ein DataSize. Drittens: Wir wollen nicht nur einen „Schlüsselwert“ testen, sondern ein ganzes Konfigurationsobjekt mit Standardwerten und Validierung.

2. @ConfigurationProperties als Vertrag

@ConfigurationProperties ist ein Mechanismus von Spring Boot, der eine Gruppe von Konfigurationsschlüsseln mit einem gemeinsamen Präfix nimmt und sie an eine Java-Klasse bindet. Statt jede Eigenschaft über @Value zu lesen, beschreiben wir die Konfigurationsstruktur einmal und verwenden sie dann wie ein normales Objekt. Es ist fast wie ein DTO, nur nicht für HTTP-Anfragen, sondern für Anwendungseinstellungen.

Im Kopf kann man es sich gut so vorstellen: YAML ist der externe Vertrag (wie eine API, nur für die Konfiguration). @ConfigurationProperties ist das Java-Modell dieses Vertrags. Und Binding ist der „Parser“, der Schlüssel und Strings aus der Environment in Felder, Daten, Zahlen, Duration, DataSize usw. umwandelt. Der Sinn ist, dass Ihr Business-Code und Infrastrukturcode sich nicht mehr mit dem „Parsen“ der Konfiguration befassen müssen – er bekommt ein fertig typisiertes Objekt.

Es ist sehr hilfreich, diesen Weg zu visualisieren:

flowchart TD
    A["application.yml / application-test.yml"] --> B["Environment PropertySources"]
    B --> C["Binder (Spring Boot)"]
    C --> D["@ConfigurationProperties bean AttachmentProperties"]
    D --> E["Service / Adapter AttachmentValidationService"]

Der entscheidende Vorteil für Tests ist, dass Sie die Schicht D separat testen können: „Wird richtig gebindet?“, „Welche Standardwerte werden angewendet?“, „Wird die Konfiguration validiert?“ – ohne dies mit HTTP, Datenbank und dem Rest der Anwendung zu vermischen. An dieser Stelle hört die Konfiguration auf, „Magie aus YAML“ zu sein, und wird zu einem Objekt, das man anfassen kann.

3. Namensgebung und relaxed binding

Nach der ersten Begegnung mit @ConfigurationProperties macht fast jeder dasselbe: Er schaut auf den YAML-Schlüssel max-count, dann auf das Feld maxCount – und denkt: „Ist das wirklich dasselbe, oder betreibe ich gerade Schamanismus?“. Gute Nachricht: Es ist kein Schamanismus, es ist so gewollt.

Spring Boot unterstützt „relaxed binding“, d. h. es kann verschiedene Schreibweisen von Namen zuordnen. In der Konfiguration wird meist kebab-case verwendet, weil es in YAML lesbarer ist: max-size, base-url, enabled. In Java natürlich camelCase: maxSize, baseUrl, enabled. Boot kann diese Varianten miteinander verbinden.

Hier eine kleine Tabelle, damit das Gehirn aufhört zu nerven:

YAML-Schlüssel Feld in Java Warum das so ist
contenthub.attachments.max-count maxCount Bindestrich in YAML ↔ camelCase in Java
contenthub.moderation.base-url baseUrl „url“ bleibt Teil des Namens
contenthub.notifications.enabled enabled stimmt in Bedeutung und Form überein

Es ist wichtig, die „Freiheit“ des relaxed binding nicht zu missbrauchen. Man könnte MAX_COUNT, max_count oder max-count schreiben, und es würde funktionieren – aber dem Leser (und Ihrem zukünftigen „Ich in einem Monat“) würde es wehtun. Daher halten wir im Kurs Disziplin: In YAML kebab-case, in Java camelCase.

4. Beispiel ContentHub: Einstellungsgruppen

Wenn Sie die Konfiguration entwerfen, denken Sie nicht „welche Eigenschaften brauche ich?“, sondern „welche Subsysteme habe ich?“. In ContentHub wird das besonders deutlich, weil das Projekt bewusst production-like gestaltet ist: Es gibt ein Dateispeicher, Attachment-Limits, einen Moderation-Client, Benachrichtigungen über Veröffentlichungen. Jedes Subsystem hat seine eigenen Einstellungen, und es ist logisch, sie unter eigenen Präfixen zu halten.

In der Konfiguration sollen folgende Gruppen leben:

- contenthub.storage.* – alles, was mit dem Stammverzeichnis der Dateispeicherung zu tun hat.

- contenthub.attachments.* – Limits für Größe/Anzahl/Typen von Anhängen.

- contenthub.moderation.* – Adresse des Moderation-Service und Timeouts.

- contenthub.notifications.* – ob Benachrichtigungen aktiviert sind und wie sie funktionieren.

Für attachments ist es hier sinnvoll, eine Pause zu machen. Grundlegende Größen- und Mengenlimits können direkt in AttachmentProperties gehalten werden, und YAML wird nur dann verwendet, wenn sie wirklich für eine Umgebung überschrieben werden müssen. Daher beschränkt sich ein solcher allgemeiner Test-Snapshot auf Storage, Moderation und Notifications:

# src/test/resources/application-test.yml

contenthub:
  storage:
    # In Tests speichern wir Dateien in build/, um die „Produktions“-Verzeichnisse nicht zu berühren
    root: "build/test-storage"
  moderation:
    # In der Testumgebung schauen wir oft auf einen lokalen Mock/Stub
    base-url: "http://localhost:8089"
    # Duration: Zeiteinheiten werden auch „menschenlesbar“ gelesen
    timeout: 250ms
  notifications:
    # Für die meisten Tests ist es einfacher, Benachrichtigungen zu deaktivieren
    enabled: false

Beachten Sie die „menschliche“ Lesbarkeit. Selbst ohne Java-Kenntnisse ist ersichtlich: In Tests schreiben wir Dateien nach build/test-storage, Moderation „schaut“ auf eine lokale URL, Benachrichtigungen sind deaktiviert. Das ist ein wichtiger psychologischer Effekt: Die Testkonfiguration liest sich wie eine Geschichte, nicht wie eine Sammlung zufälliger Zahlen.

Mit attachments ergibt sich eine weitere nützliche Geschichte: Wenn diese Schlüssel nicht in der externen Konfiguration vorhanden sind, werden die Limits aus den Feldern von AttachmentProperties übernommen, und das kann bereits mit einem separaten Test geprüft werden.

5. AttachmentProperties: Standardwerte

@ConfigurationProperties macht man in der Regel so langweilig wie möglich. Und das ist ein Kompliment. Hier sollte keine Geschäftslogik, keine Berechnungen und keine „intelligenten“ Methoden stehen. Diese Klasse ist ein Container für Einstellungen. Je einfacher, desto besser: Sie muss selbst am Montagmorgen verständlich sein, wenn der Kaffee noch nicht gewirkt hat.

Beginnen wir mit den Attachment-Limits. Wichtig ist, dass die Typen korrekt sind: Größe ist DataSize, Anzahl ist int. Und dass es im Code eine sichere Baseline gibt: Wenn die Einstellung nicht angegeben wurde, wollen wir trotzdem kein „unbegrenzt“.

import jakarta.validation.constraints.Min;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.util.unit.DataSize;
import org.springframework.validation.annotation.Validated;

@ConfigurationProperties("contenthub.attachments")
@Validated // Aktiviert die Jakarta Bean Validation beim Binding
public class AttachmentProperties {

    // Standard: sicheres Limit für die Größe, falls nicht in YAML angegeben
    private DataSize maxSize = DataSize.ofMegabytes(5);

    // Standard: sicheres Limit für die Anzahl
    @Min(1) // Konfiguration sind auch Eingabedaten: verbietet 0 und negative Werte
    private int maxCount = 3;

    // getters/setters
}

Hier stecken zwei wichtige Ideen. Erstens: Standardwerte direkt in den Feldern. Das ist praktisch: Die Klasse hat eine „Grundausstattung“, und die Anwendung fällt nicht auseinander, wenn Sie einen Schlüssel in YAML vergessen. Zweitens: Validierung über @Min(1) und @Validated. Denn Konfiguration sind auch Eingabedaten, nur nicht vom Benutzer, sondern von der Umgebung. Und wenn die Umgebung Müll liefert, wollen wir das sofort erfahren, nicht erst nach drei Stunden Debugging.

Und noch ein praktischer Punkt, der Anfänger oft auf dem falschen Fuß erwischt: Die @ConfigurationProperties-Klasse muss ein Spring-Bean werden. Das heißt, sie muss registriert werden. Der einfachste und „kurseigene“ Weg ist, das Scannen von Properties in der Anwendung zu aktivieren:

import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.context.properties.ConfigurationPropertiesScan;

@SpringBootApplication
@ConfigurationPropertiesScan // Sagt Spring Boot, dass es @ConfigurationProperties-Klassen suchen und registrieren soll
public class ContentHubApplication {
}

Wenn @ConfigurationPropertiesScan vergessen wird, erhalten Sie einen sehr ehrlichen Fehler wie „No qualifying bean of type AttachmentProperties“. Das ist nicht, weil Spring „mimt“ – es versucht Ihnen zu sagen: „Ich kenne diese Klasse nicht, ich habe sie nicht erstellt“.

6. Duration und DataSize

Sobald Sie Timeouts und Größen in der Konfiguration speichern, entsteht die Versuchung, sie als Strings zu behandeln: "250ms", "5MB", "2s". Und das funktioniert ... aber nur bis zu dem Moment, wo im Code manuell geparst wird. Manuelles Parsen im Jahr 2026 ist etwa so, wie seinen eigenen ArrayList zu schreiben: möglich, aber besser nicht ohne Grund.

Spring Boot kann Werte aus Properties in Duration und DataSize konvertieren. Das bedeutet zwei erfreuliche Dinge. Erstens: Ihr Code wird typsicher, weil Sie nicht versehentlich „5MB“ und „250ms“ addieren können. Zweitens: YAML wird menschenlesbar, weil Sie die Einheiten direkt in der Konfiguration angeben.

Beispiel für Moderation-Einstellungen:

import jakarta.validation.constraints.NotBlank;
import java.time.Duration;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.validation.annotation.Validated;

@ConfigurationProperties("contenthub.moderation")
@Validated // Validierung beim Start des Kontexts
public class ModerationProperties {

    // Basis-URL zum Moderation-Service. Leerer Wert wird als Konfigurationsfehler betrachtet.
    @NotBlank
    private String baseUrl = "http://localhost:8089";

    // Timeout für Anfragen an den Moderation-Service
    private Duration timeout = Duration.ofSeconds(2);

    // getters/setters
}

Und der entsprechende YAML-Teil (z. B. in Tests):

contenthub:
  moderation:
    base-url: "http://localhost:8089"
    timeout: 250ms

Wichtig ist, dass Werte wie 250ms, 2s, 1m fast wie menschliche Sprache gelesen werden. Und im Test vergleichen Sie auch keine Strings, sondern normale Objekte:

// In Tests vergleichen wir den typisierten Wert, nicht den String aus YAML
Duration timeout = properties.getTimeout();
assertThat(timeout).isEqualTo(Duration.ofMillis(250));

Ein weiterer Bonus: Wenn der Typ Duration ist, schlägt die IDE Methoden wie toMillis() vor. Wenn Sie einen String haben, schlägt die IDE nur ... Trauer vor.

Validierung von Einstellungen: @Validated und „schnell scheitern“

Die Validierung der Konfiguration dient dem psychologischen Komfort. Sie wollen, dass die Anwendung so nah wie möglich an der Ursache des Problems scheitert. Nicht 20 Minuten nach dem Start, nicht mitten in der Bearbeitung einer Anfrage, nicht „irgendwo tief im RestClient“. Sondern direkt beim Start, mit einer verständlichen Meldung: „Ungültige Konfiguration“.

Die Mechanik ist einfach: Sie setzen @Validated auf die Properties-Klasse und fügen Jakarta-Validation-Annotationen auf die Felder. Dann validiert Spring Boot das Objekt beim Binding, und wenn Einschränkungen verletzt sind, startet der Kontext nicht.

Zum Beispiel haben wir in den Attachment-Einstellungen bereits @Min(1) auf maxCount gesetzt. In den Moderation-Einstellungen haben wir @NotBlank auf baseUrl gesetzt. Das bedeutet, dass solche Werte als ungültig betrachtet werden:

contenthub:
  moderation:
    base-url: ""   # Leerer String -> wir scheitern beim Start

Das klingt „hart“, aber es ist die richtige Härte. Eine externe Integration ohne Basis-URL ist nicht „vielleicht klären wir das später“, sondern „die Anwendung weiß physikalisch nicht, wohin sie gehen soll“. Und es ist besser, das sofort zu erfahren.

Genauso sollte man mit Limits umgehen. Die Konfiguration max-count: 0 ist fast immer ein Fehler. Es könnte ein „bewusster Versuch sein, Anhänge zu deaktivieren“, aber dann sollte man ein separates Flag enabled einführen, anstatt die Bedeutung durch ein illegales Limit auszudrücken.

7. Test des Bindings von @ConfigurationProperties

Tests für @ConfigurationProperties zu schreiben ist angenehm, weil Sie genau eine Sache prüfen: „Wenn ich diese und jene Schlüssel setze, erhalte ich dieses und jenes Objekt“. Das ist kein Test der Geschäftslogik, kein HTTP-Test, kein Repository-Test. Es ist ein Test des Konfigurationsvertrags.

Der direkteste (und für Anfänger verständliche) Weg ist, den Kontext über @SpringBootTest hochzufahren, Eigenschaften über properties = { ... } zu setzen, das Testprofil über @ActiveProfiles("test") zu aktivieren und dann einfach die Properties-Klasse zu injizieren.

import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.ActiveProfiles;
import org.springframework.util.unit.DataSize;

import static org.assertj.core.api.Assertions.assertThat;

@SpringBootTest(properties = {
        // Überschreiben die benötigten Schlüssel direkt in der Testanmerkung
        "contenthub.attachments.max-size=10MB",
        "contenthub.attachments.max-count=5"
})
@ActiveProfiles("test")
class AttachmentPropertiesBindingTest {

    @Autowired
    AttachmentProperties properties; // Injizieren das bereits gebundene typisierte Objekt

    @Test
    void bindsAttachmentLimits() {
        // Überprüfen, ob DataSize korrekt geparst wurde
        assertThat(properties.getMaxSize()).isEqualTo(DataSize.ofMegabytes(10));
        // Überprüfen, ob das numerische Limit auch angewendet wurde
        assertThat(properties.getMaxCount()).isEqualTo(5);
    }
}

Beachten Sie einige disziplinarische Punkte. Wir setzen Schlüssel in der Form, in der die Anwendung sie tatsächlich liest (contenthub.attachments.*). Wir verwenden überhaupt kein @Value – wir testen genau die Properties-Klasse als Objekt. Und wir vergleichen Typen, nicht Strings: DataSize.ofMegabytes(10) statt "10MB".

Wenn man die Feld-Standardwerte testen möchte, gibt es eine wichtige Bedingung: Die Schlüssel contenthub.attachments.* dürfen weder in application.yml, noch in application-test.yml noch in lokalen Overrides gesetzt sein. Dann bedeutet das Fehlen von Werten tatsächlich: „Nimm das, was in der Klasse selbst gesetzt ist“.

import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.util.unit.DataSize;

import static org.assertj.core.api.Assertions.assertThat;

@SpringBootTest // Keine Overrides setzen – beobachten, wie Feld-Standardwerte ohne externe Werte für diese Schlüssel funktionieren
class AttachmentPropertiesDefaultsTest {

    @Autowired
    AttachmentProperties properties;

    @Test
    void usesDefaultsWhenNoOverridesProvided() {
        // Standardwerte schützen vor „unbegrenzt“, falls Schlüssel in YAML vergessen wurden
        assertThat(properties.getMaxSize()).isEqualTo(DataSize.ofMegabytes(5));
        assertThat(properties.getMaxCount()).isEqualTo(3);
    }
}

Wenn das externe YAML trotzdem dieselben Schlüssel setzt, testet ein solcher Test nicht die Standardwerte der Klasse, sondern die Reihenfolge der Property-Quellen – das ist eine andere Aufgabe. Und ja, dieser Test hängt davon ab, dass Sie AttachmentProperties tatsächlich als Bean registriert haben und dass die Standardwerte im Code gesetzt sind. Aber genau das wollen wir absichern: „Die Klasse hat eine sichere Basiskonfiguration“.

8. Negativszenario: Ungültige Konfiguration

Negative Fälle bei @ConfigurationProperties sind etwas tückisch. Wenn die Konfiguration ungültig ist, startet der Spring-Kontext nicht – das bedeutet, eine normale @SpringBootTest-Klasse erreicht nicht einmal die @Test-Methode. Daher ist es praktischer, die Prüfung „die Anwendung muss abstürzen“ folgendermaßen durchzuführen: Starten Sie die Anwendung manuell innerhalb des Tests und überprüfen Sie, ob sie wie erwartet abstürzt.

Das sieht ungefähr so aus:

import org.junit.jupiter.api.Test;
import org.springframework.boot.WebApplicationType;
import org.springframework.boot.builder.SpringApplicationBuilder;

import static org.assertj.core.api.Assertions.assertThatThrownBy;

class AttachmentPropertiesValidationFailureTest {

    @Test
    void failsFastWhenMaxCountIsInvalid() {
        assertThatThrownBy(() ->
                new SpringApplicationBuilder(ContentHubApplication.class)
                        // Wir brauchen keinen Webserver: testen nur den Kontextstart und das Binding
                        .web(WebApplicationType.NONE)
                        .properties(
                                // Aktivieren das Testprofil und geben ein bewusst ungültiges Limit vor
                                "spring.profiles.active=test",
                                "contenthub.attachments.max-count=0"
                        )
                        .run()
        )
        // Wir legen Wert auf ein stabiles Signal: Kontext startet nicht aufgrund ungültiger Bindung/Validierung.
        .hasMessageContaining("contenthub.attachments")
        .hasStackTraceContaining("max-count");
    }
}

Die konkrete Wrapper-Klasse in der Ausnahmekette kann zwischen den Versionen von Boot und dem Binder variieren. Technisch wichtiger ist etwas anderes: Die Anwendung scheitert sofort beim Start, und aus dem Stack Trace ist ersichtlich, dass das Problem genau bei contenthub.attachments.max-count liegt, nicht irgendwo tief im Business-Code.

Und noch ein praktischer Punkt: WebApplicationType.NONE hilft hier, den Webserver nicht zu starten. Wir testen die Konfiguration, nicht HTTP. Daher macht es keinen Sinn, den Test noch teurer zu machen.

9. Typische Fehler bei der Arbeit mit @ConfigurationProperties

Fehler Nr. 1: Properties-Klasse geschrieben, aber vergessen zu registrieren.
Das häufigste Szenario: Die Klasse wurde mit @ConfigurationProperties markiert, Felder geschrieben, dann @Autowired AttachmentProperties – und „No qualifying bean“. Im Kopf fühlt es sich an wie „Spring mag mich nicht“, aber in Wirklichkeit ist es einfach eine Tatsache: Spring erstellt kein Bean, von dem es nichts weiß. Meistens wird es durch @ConfigurationPropertiesScan auf ContentHubApplication oder explizites @EnableConfigurationProperties in der Konfiguration gelöst (letzteres ist aber meist weniger bequem, wenn viele Klassen vorhanden sind).

Fehler Nr. 2: Statt Duration und DataSize String verwenden und dann manuell parsen.
Das beginnt meist mit dem Gedanken „was soll da zu parsen sein, ein String“. Dann kommt ein zweites Format (Sekunden vs. Millisekunden), dann ein drittes, dann Tests für den Parser, dann ein Bug „5mb wurde nicht erkannt, weil jemand Kleinbuchstaben verwendet hat“. Spring Boot kann das bereits, also konkurrieren Sie nicht mit ihm in der Olympiade „Wer schreibt schneller ein Rad neu?“

Fehler Nr. 3: Mehrere Präfixe in einer Properties-Klasse mischen.
Zum Beispiel in einer Klasse sowohl storage.root, moderation.base-url als auch attachments.max-size halten. Anfangs scheint es praktisch: „Eine Klasse – alle Einstellungen“. Dann stellt sich heraus, dass diese Klasse überall benötigt wird, Tests ziehen sie überall hin, und Sie erhalten eine konfigurative „Gott-Kapsel“. Eine Klasse – ein Präfix, ein Subsystem, eine Verantwortung. Das vereinfacht sowohl das Lesen als auch das Testen erheblich.

Fehler Nr. 4: Validierung (@Validated) vergessen und erwarten, dass die Annotationen zur Einschränkung von selbst wirken.
@Min(1) auf ein Feld setzen, aber @Validated nicht auf die Klasse setzen – das ist, wie eine Alarmanlage zu installieren, aber die Batterien nicht einzulegen. Die Einschränkung existiert formal, aber niemand führt sie aus. In der Folge startet die Anwendung mit einer fehlerhaften Konfiguration, und die Probleme treten spät und unangenehm auf.

Fehler Nr. 5: Standardwerte nur in YAML hinterlegen und den Code in eine „leere Hülle“ verwandeln.
Wenn die Standardwerte nur in YAML leben, wird Ihre Java-Klasse keine eigenständige Modellklasse, sondern nur ein Spiegel der Datei. Das ist nicht immer schlecht, verringert aber oft die Stabilität: Es reicht, einen Schlüssel in einer Umgebung zu vergessen, und die Anwendung ändert ihr Verhalten. Sichere Standardwerte für wichtige Limits und Timeouts sind meist besser im Code aufgehoben, während YAML zur Überschreibung für die Umgebung dient.

1
Aufgabe
Spring Test, Level 7, Lektion 3
Gesperrt
Binding `AttachmentProperties`
Binding `AttachmentProperties`
1
Aufgabe
Spring Test, Level 7, Lektion 3
Gesperrt
Standardwerte in `ModerationProperties`
Standardwerte in `ModerationProperties`
Kommentare
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION