Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Destructoring ist die Zukunft von Javas Encapsu...

Destructoring ist die Zukunft von Javas Encapsulation (v1) 🇩🇪 @JavaLand 2026

Präsentiert auf der JavaLand 2026

----

Dekonstruktion – die Zerlegung eines Objekts in seine Bestandteile – ist der Schlüssel, damit Java Kapselung und Invarianten zuverlässig wahren kann. Dieses scheinbare Paradoxon lösen wir im Vortrag auf, indem wir uns vergangene und kommende Sprach- und Plattformfeatures im JDK ansehen.

Die klassische Java-Serialisierung bricht Kapselung: Sie greift direkt auf private Felder zu, umgeht damit Konstruktoren und die dort verankerten Prüfungen. Ursache ist, dass Dekonstruktion und Rekonstruktion bislang keine first-class Citizens im JDK waren.

Genau hier setzt aktuelle Arbeit im Rahmen von JDK Project Amber an – unter dem Arbeitstitel „Derived Record/Class Creation“. Der Ansatz erlaubt es, explizit festzulegen, wie Objekte de- und rekonstruiert werden. Damit lässt sich künftig nicht nur Serialisierung sicherer und robuster gestalten; wir gewinnen auch „Withers“ (zielgerichtete, unveränderliche Kopien mit einzelnen geänderten Komponenten) für Records sowie Pattern Matching, das über reine Record-Patterns hinaus auf normale Klassen erweitert wird.

Unter diesen Voraussetzungen lohnt sich ein genauer Blick: Was ist schon da, was kommt als Nächstes – und was bedeutet das für unsere tägliche Java-Praxis?

Avatar for Richard

Richard

March 10, 2026
Tweet

More Decks by Richard

Other Decks in Programming

Transcript

  1. 10.03.26 Richard Gross (he/him) Software Archaeology richargh.de richargh.de/ richargh Hypermedia

    Modernisation Destructuring ist die Zukunft von Javas Encapsulation
  2. JDK Deserialisierung Der Code Die Deserialisierung mit der Api 1.

    public class Adult { 2. private final Instant birthdate; 3. 4. public Adult(Instant birthdate) { 5. this.birthdate = birthdate; 6. if(isYoungerThan18(birthdate)) 7. throw new Ex(“Is not 18 yet.”); 8. } 9. 10. public long age(){ /** **/ } 11. } 1. // deserialisieren 2. var adult = (Adult) objectInputStream 3. .readObject(); 4. 5. IO.println(adult.age()); // prints 17??? Slide 3 CC BY-SA richargh.de
  3. https://adtmag.com/articles/2018/05/30/java-serialization.aspx Slide 4 CC BY-SA richargh.de Probably a third of

    all Java vulnerabilities have involved serialization; it could be over half. Mark Reinold Chief Architect Java Platform Group
  4. https://adtmag.com/articles/2018/05/30/java-serialization.aspx Slide 5 CC BY-SA richargh.de Removing serialization is one

    of the goals of project amber. Mark Reinold Chief Architect Java Platform Group
  5. Third-Party Deserialisierung Der Code Die Deserialisierung mit Jackson 1. public

    class Adult { 2. private final Instant birthdate = null; 3. 4. public Adult() { } 5. 6. public Adult(Instant birthdate) { 7. this.birthdate = birthdate; 8. if(isYoungerThan18(birthdate)) 9. throw new Ex(“Is not 18 yet.”); 10. } 11. 12. public Instant getBirthdate() { 13. return this.birthdate; 14. } 15. 16. public long age(){ /** **/ } 17. } 1. // deserialisieren 2. var result = objectMapper 3. .readValue(json, Adult.class); 4. 5. IO.println(adult.age()); // prints 17??? Slide 7 CC BY-SA richargh.de
  6. https://bugs.java.com/bugdatabase/JDK-5044412 Slide 8 CC BY-SA richargh.de Stark paraphrasiert: The spec

    requires „final“ to mean immutable. This broke XML serialization in our AppServer. Probably other code as well. For compatibility we should be able to setAccessible via reflection.
  7. In beiden Fällen wurde die Encapsulation gebrochen Der Constructor wurde

    missachtet, alle guards umgangen Slide 9 CC BY-SA richargh.de
  8. Encapsulation • Ein Element garantiert, dass es immer in einem

    erlaubten Zustand ist • Invarianten beschreiben was immer gültig ist 10
  9. Wie man Encapsulation bricht Serialization Api • Serialisieren: alle privaten

    Felder aufsaugen • Private, final, kann man doch alles ignorieren • Deserialisieren: alle privaten und finalen Felder setzen, Constructor ignorieren Reflection Api (analog) Slide 11 CC BY-SA richargh.de 1. var adult = new Adult(...); 2. // Feld per reflection 3. Field birthdateField = adult 4. .getClass().getDeclaredField("birthdate"); 5. 6. birthdateField.setAccessible(true); 7. birthdateField.set(adult, seventeenYearsAgo); 8. // ^^^ 9. // deep reflection (seit JDK 5) 10. // Warnung mit JDK 26 (März 2026)
  10. Integrity by default1 Developers expect that their code and data

    is protected against use that is unwanted or unwise. (…) Going forward, we will restrict unsafe APIs so that, by default, libraries, frameworks, and tools cannot use them2. 1 https://openjdk.org/jeps/8305968 2 https://openjdk.org/jeps/500 Slide 12 CC BY-SA richargh.de
  11. Integrity by default1 Application authors will have the ability to

    override this default. 1 https://openjdk.org/jeps/8305968 Slide 13 CC BY-SA richargh.de
  12. Derzeit können wir dem gelesenen Code nicht vertrauen Zur Laufzeit

    passiert dank deep reflection etwas ganz anderes Slide 14 CC BY-SA richargh.de
  13. Was wir haben Slide 16 CC BY-SA richargh.de JVM Serialization

    Api Gebro chen Serialization Api Deep Reflection Deep Reflection
  14. Warum wir es brauchen Slide 17 CC BY-SA richargh.de Deserialisierung

    Serialisierung Binary ObjectStream Protobuf Avro BSON RESP Xml Json Yaml Toml Html Csv docx Binary ObjectStream Protobuf Avro BSON RESP Xml Json Yaml Toml Html Csv docx Java Object
  15. Die bisherigen Apis sind nicht gerade einfach Serialization Api Third-Party

    Serialization 1. readObject 2. writeObject 3. readObjectNoData 4. readResolve 5. writeReplace 6. serialVersionUID 7. serialPersistantFields Slide 18 CC BY-SA richargh.de 1. @JsonAnyGetter 2. @JsonAnySetter 3. @JsonGetter 4. @JsonSetter 5. @JsonValue 6. @JsonRawValue 7. @JsonSerialize 8. @JsonDeserialize 9. @JsonCreator 10. @JsonAlias 11. @JsonIgnoreProperties 12. @JsonIgnore 13. @JsonIgnoreType 14. @JsonInclude 15. @JsonIncludeProperties 16. uvm.
  16. Diese Apis sind nicht die Zukunft • Es gibt Pläne

    für die Serialization Api1 • Es gibt Pläne für Deep Reflection2 • Es wurde auch schon angefangen (JDK 26: Prepare to make final mean final3) 1 https://adtmag.com/articles/2018/05/30/java-serialization.aspx 2 https://openjdk.org/jeps/8305968 3 https://openjdk.org/jeps/500 Slide 19 CC BY-SA richargh.de
  17. Records lösen viele Probleme • Die Serialization Api ruft immer

    den Constructor auf1 • setAccessible(true) auf final fields scheitert2 • Accessor und Constructor-Parameter sind immer synchron à Api und interner state sind gleich 1 https://docs.oracle.com/en/java/javase/25/docs/specs/serialization/input.html#:~:text=canonical%20constructor 2 https://bugs.openjdk.org/browse/JDK-8247517 Slide 20 CC BY-SA richargh.de
  18. Klassen bieten Flexibilität durch Api-State-Entkopplung public class Timestamp { private

    final long rawValue; // Constructor public Timestamp(String rawUtcDate) { this.rawValue = parseUtcDateString(utcDate); } // Accessor public String utcDate(){ return formatAsUtcDateString(this.rawValue); } } Slide 21 CC BY-SA richargh.de
  19. Records sind nicht immer nutzbar Manchmal wollen wir: • State

    mutieren können • State intern hierarchisch ablegen • State intern ableiten oder cachen • State intern anders speichern als in der Klassen-Api beschrieben Slide 22 CC BY-SA richargh.de
  20. Es fehlt ein Konzept im JDK Slide 24 CC BY-SA

    richargh.de Deserialisierung Gesch ützt Serialisierung Constructor Deconstructor Noch kein first- class Citizen
  21. Deconstruction als first-class citizen 2026-01 Carrier Classes https://mail.openjdk.org/pipermail/amber-spec-experts/2026-January/004307.html 2025-03 Where

    Is the Java Language going https://www.youtube.com/watch?v=1dY57CDxR14 2024-10 Serialization a new Hope https://www.youtube.com/watch?v=fbqAyRJoQO0 2019-06 Towards better Serialization https://openjdk.org/projects/amber/design-notes/towards-better-serialization public class Timestamp { private final long rawValue; // Constructor public Timestamp(String rawUtcDate) { this.rawValue = parseUtcDateString(utcDate); } // Deconstruction pattern public pattern Timestamp(String rawUtcDate) { rawUtcDate = formatAsUtcDateString(this.rawValue); } } Slide 25 CC BY-SA richargh.de Strawman syntax Viele Syntax-Ideen stehen im Raum Syntaktisch der inverse Constructor
  22. Deconstruction ist das power- feature Es geht hier nicht um

    die Syntax, sondern was uns das Feature bringt Slide 26 CC BY-SA richargh.de
  23. Zwei-Attribut (Third-Party) Serialisation Api public class Point { private final

    int x; private final int y; @Deserializer public Point(int x, int y) { this.x = x; this.y = y; } @Serializer public pattern Point(int x, int y) { x = this.x; y = this.y; } } Slide 27 CC BY-SA richargh.de
  24. Serialisation Api mit Versionssupport public class Point { private final

    int x; private final int y; @Deserializer(version = 2) public Point(int x, int y) { this.x = x; this.y = y; } @Deserializer(version = 1) public Point(int x) { this(x, 0); } @Serializer(version = 2) public pattern Point(int x, int y) { x = this.x; y = this.y; } } Slide 28 CC BY-SA richargh.de
  25. Pattern matching für Klassen Deconstruction definieren Und matchen 1. public

    class Address { 2. 3. private final String street; 4. private final String city; 5. /* Constructor etc. */ 6. 7. // deconstruction pattern 8. public pattern Address(int street, int city){ 9. street = this.street; 10. city = this.city; 11. } 12. } Slide 29 CC BY-SA richargh.de 1. void handle(Object obj) { 2. if(obj instanceOf Address(var street, var city)){ 3. // do something with street and city 4. } 5. }
  26. Pattern matching für Factories Von verboser Deconstruction Zum kompakten Matchen

    1. // construction 2. Optional<Shape> maybeShape = Optional 3. .of(Ball.of(RED, 1)); 4. // verbose deconstruction by hand 5. Shape s = maybeShape.orElse(null); 6. if(s != null 7. && s.isBall() 8. && (s.color() == RED)){ 9. var ball = (Ball) s; 10. IO.printLn(ball.size() + " red balls"); 11. } Slide 30 CC BY-SA richargh.de 1. // construction 2. var shape? = Optional.of(Ball.of(RED, 1)); 3. // compact matcher 4. if(shape? instanceOf Optional.of(Ball.of(RED, var count))){ 5. IO.printLn(count + " red balls"); 6. }
  27. Class Withers Mit neuer Syntax Klassen rekonstruieren 1. // strawman

    canonical constructor 2. class Address(String street, String city) { 3. 4. // strawman assignment syntax 5. private final String street = street; 6. private final String city = city; 7. 8. // strawman compact constructor 9. Address { 10. notBlank(street); 11. notBlank(city); 12. } 13. 14. // deconstruction pattern 15. pattern Address(int street, int city) { 16. street = this.street; 17. city = this.city; 18. } 19. } Slide 31 CC BY-SA richargh.de 1. var newAddress = anAddress with { 2. street = "Cool Blvd"; 3. city = "Cool City"; 4. } Canonical Constructor für Klassen
  28. Vom manuellen with mit Records Jedes with manuell schreiben Records

    manuell rekonstruieren 1. record Address(String street, String city){ 2. Address { 3. notBlank(street); 4. notBlank(city); 5. } 6. 7. Address withStreet(String street){ 8. return new Address(street, this.city); 9. } 10. 11. Address withCity(String city){ 12. return new Address(this.street, city); 13. } 14. } Slide 32 CC BY-SA richargh.de 1. var newAddress = anAddress 2. .withStreet("Cool Blvd”) 3. .withCity("Cool City”);
  29. Zu Record Withers1 1 aka Derived Record Creation https://openjdk.org/jeps/468 Kein

    with mehr schrieben Records automatisch rekonstruieren 1. record Address(String street, String city){ 2. Address { 3. notBlank(street); 4. notBlank(city); 5. } 6. } Slide 33 CC BY-SA richargh.de 1. var newAddress = anAddress with { 2. street = "Cool Blvd"; 3. city = "Cool City"; 4. }
  30. Fragen? richargh.de Richard Gross (he/him) Software Archaeology Hypermedia Modernisation Works

    for maibornwolff.de/ richargh.de richargh Slide 36 CC BY-SA richargh.de https://content.maibornwolff.de/meetings/richard-gross Trink ein (virtuelles) Heißgetränk mit mir. * Alle Code Beispiele https://github.com/Richargh/encapsulation-java-mvn-sandbox
  31. Jackson ohne Default-Constructor See https://github.com/Richargh/encapsulation-java-mvn- sandbox/blob/trunk/src/test/java/de/richargh/sandbox/encapsulation/jackson/JacksonClassMapperTest.java#L190 Klasse mit einem Constructor

    Mit ParameterNamesModule und CompilerFlag gehts ohne no-args 1. public class Address { 2. 3. private final String street; 4. private final String city; 5. 6. // constructor for deserialization 7. public Address(String street, String city){ 8. this.street = Validate.notBlank(street); 9. this.city = Validate.notBlank(city); 10. } 11. 12. // two getters for serialization 13. public String getStreet(){ return this.street; } 14. public String getCity(){ return this.city; } 15. } 16. Slide 38 CC BY-SA richargh.de 1. // Requires ParameterNamesModule in Jackson 2.x 2. // and the compilerArg: -parameters. 3. var mapper = JsonMapper.builder() 4. .addModule(new ParameterNamesModule()) 5. .build(); 6. var result = objectMapper.readValue(json, Address.class);