About the possibility of using JEP468 to inspire optional fields in records for domain contracts modeling.

Ethan McCue ethan at mccue.dev
Tue Mar 11 17:40:59 UTC 2025


Unfortunately there is nothing which will auto-derive a User(String
name) { this(name, null, ... more nulls ...); } constructor for you.
Part of the problem is that "optional to specify" doesn't always mean
"has a default of null."

Example:


record User(String name, String email, List<Pet> pets) {
    User {
        Objects.requireNonNull(pets);
    }

    User(String name) {
        this(name, null, List.of())
    }

}

In this case both email and pets are optional to specify, but null
isn't actually the value you want for an unspecified list of pets.

The proposal for nullable and null-restricted types might be of
interest to you (https://openjdk.org/jeps/8303099), but absent
user-land code generation (with a heuristic like nullable == optional
to specify) I don't imagine there could be a mechanism for "optional
record components" that wouldn't also encroach upon the more general
problem of "named method invocation with default values." And that is
a tough problem.



On Tue, Mar 11, 2025 at 12:29 PM david Grajales <
david.1993grajales at gmail.com> wrote:

> Indeed that might solve the problem (as it would also solve the issues JEP
> 468 tries to address btw) but it's not the actual matter i wanted to point
> out. This matter is more about data modeling and the use-case is just a
> personal example for explanation. The real issue is currently records are
> the java construct to model data but they lack the ergonomics and
> semantics  to tell which fields are strictly mandatory and necessary other
> than creating static methods that acts like custom constructors, they
> assume all fields are mandatory and must be set manually (even to null).
> Maybe they should have those ergonomics and semantics or maybe they
> shouldn't and are much less important than I assume.
>
> I am not a language designer so I am not going to discuss further since I
> am very confident the amber team members know best.
>
> Best regards and always yours.
>
>
>
> El mar, 11 mar 2025 a la(s) 10:29 a.m., Anatoly Kupriyanov (
> kan.izh at gmail.com) escribió:
>
>> Does the stuff like https://github.com/Randgalt/record-builder solve the
>> problem? It doesn't sound as it needs changes in the language.
>>
>> On Tue, 11 Mar 2025, 14:48 david Grajales, <david.1993grajales at gmail.com>
>> wrote:
>>
>>> Hi Amber team. I would like to share some thoughts based on real use
>>> cases I am dealing with at work.
>>>
>>>
>>> I know is very likely you have already thought about something like this and I don't know if this would be the best solution for the use case presented.
>>>
>>> My intention is not to propose this as "the solution" but instead just present a personal use-case based on my actual job and a naive solution that crossed my mind in the first 15 minutes.
>>>
>>> So please pay more attention to the use case and "the problem" rather than the proposed "solution" which is actually just a mean to explain the problem
>>>
>>>
>>> Nowadays to create a new record object we must set the value to all it
>>> fields, even the ones that might be null.
>>>
>>> record User (String name, String email){}
>>> var userWithoutEmail = new User ("name", null)
>>>
>>> This is good since it forces us to set all fields to a valid state and
>>> null can be a valid state. This is useful for things like JSON
>>> serialization; null fields in JSON are usually not serialized, which saves
>>> some bandwidth, in very large JSON structures that only have an small set
>>> of mandatory fields, this is very common in banks that happens to be
>>> international, since they usually use the same Core for all countries but
>>> each country may have different requirements (for example in some countries
>>> it's mandatory for people to provide 2 lastnames but in other countries
>>> usually you only have one lastname).
>>>
>>> To achieve this I usually do the opposite of withers, let me introduce
>>> you to the "withouts"
>>>
>>> record User (String name, String email){
>>>
>>>    public static withoutEmail(String name){
>>>
>>>       return new User(name, null);
>>>    }
>>> }
>>> var userWithoutEmail = User.withoutEmail(name);
>>>
>>> The example is very basic for explanatory purposes and for a record with
>>> only 2 fields this can be an overkill, but for bigger records it makes a
>>> lot of sense, specially if only few fields between dozens are actually
>>> mandatory.
>>>
>>> Why not use a class instead? Serialization. records' serialization uses
>>> the record constructor, which means it's safer since you can make format
>>> and safety validations in the data before the record is built.
>>>
>>> nowadays to make sure all the mandatory fields are set, I do something
>>> like this.
>>>
>>> record User (String name, String email){
>>> User{
>>> if(name == null)
>>> throw new IllegalArgumentException("name field it's mandatory");
>>> }
>>> }
>>>
>>>
>>> With derived record creation (or something equivalent but for direct
>>> creation) and nullability it would be possible and very handy to be able to
>>> declare optional fields that would compile to null (or zero in case of
>>> primitives)
>>>
>>>
>>> *Please ignore the straw man syntax from here.*
>>>
>>> record User (String name!, String email? ){}
>>> User userWithoutEmail with { name : "name"}
>>>
>>> This would make this feature in java to behave similar to TypeScript's
>>> interfaces, which are used to model data
>>>
>>> export interface User {
>>>    name: String!
>>>    email: String?
>>> }
>>>
>>>
>>> To me one of the most useful use cases of this would be for writing
>>> unitary tests and mocks.
>>>
>>> when writing tests sometimes you want to test the behaviour of your
>>> contracts (the domain objects) when they carry only the bare required
>>> fields, if one has very large json objects to try out but only a small set
>>> of mandatory fields usually it's easier to write down the json string and
>>> pass it to jackson or gson, with this we could use the domain object
>>> directly.
>>>
>>>
>>> Regular test if the domain object has many optional fields
>>>
>>>
>>> record Message (String id, String document, String name, String lastname, String email.... (another 20 fields)){}
>>>
>>>
>>> var request =
>>> """{
>>>   id: "XXXXXXX",
>>>   document: "NNN-NNNNN"
>>> }"""
>>>
>>>
>>> var message = gson.fromJson(request, Message.class);
>>> var res = method2Test(message);
>>> // put your favorite assertion here//
>>>
>>>
>>> this has the issue you don't have help from the compiler in case you want to represent different scenarios (formatting data, invalid fields, etc)
>>>
>>> this is why I usually do "whitouts" for this kind of records.
>>>
>>>
>>> record Message (String id, String document, String name, String lastname, String email.... (another 20 fields)){
>>>
>>>   public Static Message minimalRequest(String id, String document){
>>>
>>>      return new Message (id, document, lastname, email, null, null...);
>>>
>>>    // so I have to write all those nulls once
>>>
>>>    }
>>>
>>> }
>>>
>>> var request = Message.minimalRequest(.....);
>>>
>>> var res = method2Test(message);
>>>
>>> // put your favorite assertion here//
>>>
>>>
>>> With optional fields in records there could be changed for something
>>> like this.
>>>
>>> record Message (String id, String document, String name?, String lastname?, String email? .... (another 20 nullable fields)){}
>>>
>>> Message request  with {id: "XXXXXXXX"; document: "NNN-NNNNNN"}
>>> var res = method2Test(message);
>>> // put your favorite assertion here//
>>>
>>> I hope this presented use-case it's useful
>>>
>>> Best regards!
>>>
>>>
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <https://mail.openjdk.org/pipermail/amber-dev/attachments/20250311/863aec33/attachment.htm>


More information about the amber-dev mailing list