Search code examples
javaoption-typerecordjava-17

Optional<T> as a Record Parameter in Java


The Optional<T> type introduced in Java 8 is mostly recommended to be used for return types and results. Hence, whenever I use it in a class field or a method parameter, I get a warning in IntelliJ:

Optional<?> used as type for field / parameter.

However, I do not get this warning when I use an Optional<T> as a record parameter in the canonical constructor:

public record AuthenticationResult(
    boolean isAuthenticated,
    Optional<User> user,
    Optional<String> authorizationHeader)
{ } 

Does the practice of not using Optional<T> types as parameters/fields not apply to record parameters because the field will always be accessed as a return value of the autogenerated getter method of the record?

Or is it because records are a new feature and this warning is yet to be implemented, and using an optional parameter in records has the same consequences as when used as a method parameter or field?


Solution

  • I would not attempt to explain why the current version of IntelliJ doesn't issue a warning for a record having optional fields (that's a question for developers of the IDE).

    In this post, I'm addressing the question regarding recommended practices of using Optional, in particular with Java 16 Records.

    Does the practice of not using Optional<T> types as parameters/fields not apply to record parameters

    Firstly, Optional is not intended to be used as a field type, for that reason Optional doesn't implement Serializable (see). Secondly, records are data-carriers, their fields are final. Hence, if a record gets initialized with optionals that are empty, they would stay empty throughout all its time-span.

    Usage of Optional

    Here's a quote from the answer by @StuartMarks, Java and OpenJDK developer, regarding what Optional is meant to be used for:

    The primary use of Optional is as follows:

    Optional is intended to provide a limited mechanism for library method return types where there is a clear need to represent "no result," and where using null for that is overwhelmingly likely to cause errors.

    The only valid usage for Optional is returning it from a method where it was originated. And the caller should immediately unpack the optional (API offers plenty of methods for that). But if you're passing the optional object around and storing it somewhere, then what you're doing isn't a good practice.

    Optional is not meant for

    Optional is not meant to be used:

    • As a field type;
    • As a type of the method parameter;
    • To be stored in a Collection;
    • Or utilized to perform null-checks. Substituting explicit null-check with Optional.ofNullable() is an antipattern.

    Here's a quote from the answer by @Brian Goetz, Java Language Architect (Should Java 8 getters return optional type?):

    Of course, people will do what they want...

    For example, you probably should never use it for something that returns an array of results, or a list of results; instead return an empty array or list. You should almost never use it as a field of something or a method parameter.

    Also, have a look at this answer by StuartMarks, here's a small quote:

    having an Optional in a class field or in a data structure, is considered a misuse of the API. First, it goes against the main design goal of Optional as stated at the top. Second, it doesn't add any value.

    Records are Transparent carriers for Immutable data

    An object with Optional fields forces the one who deals with it always take into consideration that the object obtained via a getter isn't a value, but potentially empty optional, which might throw NoSuchElementException if you blindly invoke get() on it.

    Also, having fields of Optional type in a Record is in contradiction with the general concept of Records.

    Here's a definition of Record from the JEP 395:

    records, which are classes that act as transparent carriers for immutable data. Records can be thought of as nominal tuples.

    A record with Optional fields is no longer transparent, because we're not able to get a value directly from it via accessor method.

    Since record fields are immutable, it doesn't make sense to store potentially empty optionals as record properties, it almost the same as storing null-references. Because fields in a record can't be changed, an empty optional will remain empty, and some of your tuples might not contain useful data at all.

    There's no advantage in passing around optionals, storing them inside records in order to find out at a later point in time that they don't contain the actual data.

    Instead, you have to extract a value (if present) from an Optional object obtained from somewhere right on the spot before creating a record.