Skip to content

Remove redundant cast of optional record property wither methods#1616

Merged
elucash merged 1 commit intoimmutables:masterfrom
werli:fix-build-with-optional
Nov 11, 2025
Merged

Remove redundant cast of optional record property wither methods#1616
elucash merged 1 commit intoimmutables:masterfrom
werli:fix-build-with-optional

Conversation

@werli
Copy link
Copy Markdown
Contributor

@werli werli commented Nov 10, 2025

Context

This pull request can be seen as follow up of #1589 (cc @AndreiPurcaru) and tries to unblock using the neat record "wither" interface for optional values as introduced in https://github.com/immutables/immutables/releases/tag/2.11.0 when using strict checkstyle configuration.

I didn't find referencing issues or pull requests. Since the solution looked feasible I just went for it. 🚀

Bug description

import java.util.Optional;
import org.immutables.value.Value;

@Value.Builder
record MinimalExample(Optional<String> value) implements WithMinimalExample {}

generates (among other methods)

  /**
   * Copy the record by setting an optional value for the {@code value} attribute.
   * An equality check is used on inner value to prevent copying of the same value by returning {@code this}.
   * @param optional An optional value for value
   * @return A modified copy of the record or {@code this} if not changed
   */
  default MinimalExample withValue(Optional<String> optional) {
    Optional<String> newValue = Objects.requireNonNull((Optional<String>) optional, "value optional");
    MinimalExample self = (MinimalExample) this;
    if (self.value().equals(newValue)) return self;
    return new MinimalExample(newValue);
  }

optional is however already of type Optional<String> so the strict checkstyle configuration in my project fails the build due to the redundant cast.

[WARNING] COMPILATION WARNING : 
[INFO] -------------------------------------------------------------
[WARNING] WithMinimalExample.java:[33,56] redundant cast to java.util.Optional<java.lang.String>
[INFO] 1 warning
[INFO] -------------------------------------------------------------
[INFO] -------------------------------------------------------------
[ERROR] COMPILATION ERROR : 
[INFO] -------------------------------------------------------------
[ERROR] warnings found and -Werror specified

Solution description

Conceptually simple: Only cast when strictly necessary. As stated by the documentation of org.immutables.value.processor.meta.ValueAttribute#isSafeUncheckedCovariantCast:

If we don't have an extends, then we don't need a cast, and we don't need to suppress any unchecked warnings.

So the diff for the minimal example above (where the cast is redundant) should look like:

diff a/WithMinimalExample.java b/WithMinimalExample.java
--- a/WithMinimalExample.java
+++ b/WithMinimalExample.java
@@ -30,7 +30,7 @@ public interface WithMinimalExample {
    * @return A modified copy of the record or {@code this} if not changed
    */
   default MinimalExample withValue(Optional<String> optional) {
-    Optional<String> newValue = Objects.requireNonNull((Optional<String>) optional, "value optional");
+    Optional<String> newValue = Objects.requireNonNull(optional, "value optional");
     MinimalExample self = (MinimalExample) this;
     if (self.value().equals(newValue)) return self;
     return new MinimalExample(newValue);

But for cases where the cast is necessary due to dealing with covariants we of course need to retain it:

  default RecordOptionals withObj(Optional<? extends Object> optional) {
    @SuppressWarnings("unchecked") // safe covariant cast
    Optional<Object> newValue = Objects.requireNonNull((Optional<Object>) optional, "obj optional");

I opted to use org.immutables.value.processor.meta.ValueAttribute#consumesWildcardExtends for these reasons:

  1. we're already in the context of an optional property
  2. it's used in suppressCovariantCastForOptional (which is used to generate the unchecked cast warning suppression)
  3. it's used for similar use-cases like here or here to avoid redundant casting already

Maybe there are better solutions?

@elucash
Copy link
Copy Markdown
Member

elucash commented Nov 11, 2025

Thank you for the fix! If it is that easy, I wonder why I've overlooked it ;)

@elucash elucash merged commit d2ac4f1 into immutables:master Nov 11, 2025
16 checks passed
@elucash elucash added this to the 2.11.7 milestone Dec 8, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants