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

Destructoring is the Future of Javas Encapsulat...

Sponsored · SiteGround - Reliable hosting with speed, security, and support you can count on. →
Avatar for Richard Richard
October 04, 2026

Destructoring is the Future of Javas Encapsulation (v1) 🇬🇧 @Devoxx Be 2026

Destructoring—the breakdown of an object into its constituent parts—is key to Java's ability to reliably maintain encapsulation and invariants. We resolve this apparent paradox in the presentation by looking at past and future language and platform features in the JDK.

Classic Java serialization breaks encapsulation: it directly accesses private fields, thereby bypassing constructors and the checks anchored there. The reason for this is that deconstruction and reconstruction have not been first-class citizens in the JDK until now.

This is precisely where current work within the JDK Project Amber comes in – under the working title “Derived Record/Class Creation.” This approach allows us to explicitly specify how objects are deconstructed and reconstructed. This will not only make serialization more secure and robust in the future, but we will also gain “withers” (targeted, immutable copies with individual modified components) for records and pattern matching, which will be extended beyond pure record patterns to normal classes.

Given these circumstances, it is worth taking a closer look: What is already there, what comes next – and what does this mean for our daily Java practice?

Avatar for Richard

Richard

October 04, 2026

More Decks by Richard

Other Decks in Programming

Transcript

  1. Destructuring ist the Future of Javas Encapsulation Richard Gross (he/him)

    richargh.de/ richargh.de 05.10.26 Software Modernisation Hypermedia Archaeology richargh
  2. Can we trust Code, that was deserialised by the built-in

    JDK Api? Slide 2 CC BY-SA richargh.de
  3. JDK Deserialisation Api Code Deserialising it via 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
  4. Probably a third of all Java vulnerabilities have involved serialization;

    it could be over Mark Reinold half. Chief Architect Java Platform Group Slide 4 CC BY-SA richargh.de https://adtmag.com/articles/2018/05/30/java-serialization.aspx
  5. Removing serialization is one of the goals of project amber.

    Mark Reinold Chief Architect Java Platform Group Slide 5 CC BY-SA richargh.de https://adtmag.com/articles/2018/05/30/java-serialization.aspx
  6. Third-Party Deserialisation Code Deserialising it via 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
  7. Heavily paraphrased: 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. Slide 8 CC BY-SA richargh.de https://bugs.java.com/bugdatabase/JDK-5044412
  8. In both Cases the Encapsulation was broken Der Constructor was

    ignored, alle guards were bypassed Slide 9 CC BY-SA richargh.de
  9. Encapsulation • An Element guarantees, that it is always in

    a valid State • Invariants describe what is always valid 10 https://blogs.oracle.com/javamagazine/post/quiz-yourself-apply-encapsulation-principles-to-a-class
  10. How to break Encapsulation Serialisation Api • Serialisation: gobble up

    all private Fields • Private, final, can all be ignored • Deserialisation: set all private and final fields directly, ignore the constructor Slide 11 CC BY-SA richargh.de Reflection Api (analogous) 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 (since JDK 5) 10. // Warning since JDK 26 (March 2026)
  11. 1 Integrity by default 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. Slide 12 CC BY-SA richargh.de 1 https://openjdk.org/jeps/8305968 2 https://openjdk.org/jeps/500
  12. 1 Integrity by default Application authors will have the ability

    to override this default. Slide 13 CC BY-SA richargh.de 1 https://openjdk.org/jeps/8305968
  13. Right now we cannot trust the code we read During

    runtime deep reflection enables unwanted or unwise behavior Slide 14 CC BY-SA richargh.de
  14. What we have JVM Serialization Api Serialization Api Deep Reflection

    Slide 16 CC BY-SA richargh.de Broken Deep Reflection
  15. Why we need it Deserialisation Binary ObjectStream Protobuf Avro BSON

    RESP Xml Json Yaml Toml Html Csv docx Slide 17 CC BY-SA richargh.de Java Object Serialisation Binary ObjectStream Protobuf Avro BSON RESP Xml Json Yaml Toml Html Csv docx
  16. The current Apis are not exactly easy Serialisation Api Third-Party

    Serialisation 1. 2. 3. 4. 5. 6. 7. 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. readObject writeObject readObjectNoData readResolve writeReplace serialVersionUID serialPersistantFields Slide 18 CC BY-SA richargh.de
  17. These Apis are not the future • Plans exist for

    the Serialisation Api1 • Plans exist for Deep Reflection2 • And they have already started (JDK 26: Prepare to make final mean final3) Slide 19 CC BY-SA richargh.de 1 https://adtmag.com/articles/2018/05/30/java-serialization.aspx 2 https://openjdk.org/jeps/8305968 3 https://openjdk.org/jeps/500
  18. Records solve many problems • The Serialisation Api always calls

    their Constructor1 • setAccessible(true) on final fields fails2 • Accessor and Constructor-parameter are always in sync à Api and internal state are the same Slide 20 CC BY-SA richargh.de 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
  19. Classes provide flexibility through Api-State-Decoupling 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
  20. Records are not always usable Sometimes we want to: •

    Store state differently than in the class Api • Derive or cache state internally • Mutate state in-place Slide 22 CC BY-SA richargh.de
  21. The JDK is missing a concept Deserialisation Constructor Protec ted

    Deconstructor Not a first-class Citizen Slide 24 CC BY-SA richargh.de Serialisation
  22. Deconstruction as a first-class citizen 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); } } Strawman syntax The inverse Constructor syntax-wise Many other syntax-ideas are being discussed Slide 25 CC BY-SA richargh.de 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
  23. Deconstruction is the powerfeature It’s not about the syntax, it

    is what the feature will bring Slide 26 CC BY-SA richargh.de
  24. Two-attribute built-in&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
  25. Serialisation with version support 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
  26. Pattern matching for classes Define deconstruction And match 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. } 1. void handle(Object obj) { 2. if(obj instanceOf Address(var street, var city)){ 3. // do something with street and city 4. } 5. } Slide 29 CC BY-SA richargh.de
  27. Pattern matching for Factories From verbose Deconstruction To compact Matching

    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. } 1. 2. 3. 4. 5. 6. Slide 30 CC BY-SA richargh.de // construction var shape? = Optional.of(Ball.of(RED, 1)); // compact matcher if(shape? instanceOf Optional.of(Ball.of(RED, var count))){ IO.printLn(count + " red balls"); }
  28. Class Withers Use new Syntax Canonical constructor for classes 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 To reconstruct classes 1. 2. 3. 4. var newAddress = anAddress with { street = "Cool Blvd"; city = "Cool City"; }
  29. Manual with methods in records From write every with yourself

    And reconstruct records manually 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. } 1. 2. 3. Slide 32 CC BY-SA richargh.de var newAddress = anAddress .withStreet("Cool Blvd”) .withCity("Cool City”);
  30. Record withers1 To just defining a record And get automatic

    reconstruction 1. 2. 3. 4. 5. 6. 1. 2. 3. 4. record Address(String street, String city){ Address { notBlank(street); notBlank(city); } } Slide 33 CC BY-SA richargh.de var newAddress = anAddress with { street = "Cool Blvd"; city = "Cool City"; } 1 aka Derived Record Creation https://openjdk.org/jeps/468
  31. Questions? Richard Gross (he/him) Software Archaeology Modernisation Software Modernization Hypermedia

    richargh.de richargh.de richargh Works for maibornwolff.de/ Let’s drink a (virtual) hot beverage. Or iced matcha, also ok. https://www.maibornwolff.de/en/software-modernization/ Slide 36 CC BY-SA richargh.de * All code examples https://github.com/Richargh/encapsulation-java-mvn-sandbox
  32. Jackson without a Default-Constructor Class with one Constructor 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 Avoid no-args by adding ParameterNamesModule and CompilerFlag 1. 2. 3. 4. 5. 6. // Requires ParameterNamesModule in Jackson 2.x // and the compilerArg: -parameters. var mapper = JsonMapper.builder() .addModule(new ParameterNamesModule()) .build(); var result = objectMapper.readValue(json, Address.class); See https://github.com/Richargh/encapsulation-java-mvnsandbox/blob/trunk/src/test/java/de/richargh/sandbox/encapsulation/jackson/JacksonClassMapperTest.java#L190