At my org we extensively use @Value.Immutable annotation to generate Immutable classes (usually with builders) for our internal data transfer objects (DTOs). In our codebase I've seen both interfaces and abstract classes being used for such DTO definitions.
For example for a DeviceData DTO we could have an interface as follows
@Value.Immutable @JsonDeserialize(as = ImmutableDeviceData.class) public interface DeviceData { @Value.Parameter DeviceIdentifier deviceIdentifier(); @Value.Parameter DeviceType deviceType(); // enum } or equivalently we could have abstract class with identical body as above interface
public abstract class DeviceData {
Either ways, we instantiate the DTO as follows
final var myDeviceData = ImmutableDeviceData.builder() .deviceIdentifier(ImmutableDeviceIdentifier.of("xxxx-xxxx") .deviceType(DeviceType.Tablet) .build(); At times we also add certain precondition checks using @Value.Check annotations such as following, which again work identically with both interface and abstract classes
@Value.Immutable(builder = false) public abstract class DeviceIdentifier { @Value.Parameter public String value(); @Value.Check default void validate() { Validate.notBlank(value(), "DeviceIdentifier cannot be blank"); } } Additionally there are situations where we have to declare static fields such as regex Pattern in case of EmailAddress DTO; here again Java17 makes it possible in both abstract class and interfaces alike.
Considering this specific use case of Immutable data-transfer objects, are there any pros or cons of preferring abstract class over interface or vice-versa?
interfacevsclassis barely distinguishable.records is that there's limited support for implementing builder-pattern with them [ref-1, ref-2]. We have presently decided to userecords for the 'tiny' DTOs such asCoordinate, but for complex ones having manyOptionals we are continuing to useabstract class