Repository: googleapis/java-storage
Pull request #2074 (2023-06-16 14:17:03)

deps: update dependency org.graalvm.buildtools:native-maven-plugin to v0.9.23
[![Mend Renovate](https://app.renovatebot.com/images/banner.svg)](https://renovatebot.com)

This PR contains the following updates:

| Package | Change | Age | Adoption | Passing | Confidence |
|---|---|---|---|---|---|
| [org.graalvm.buildtools:native-maven-plugin](https://togithub.com/graalvm/native-build-tools) | `0.9.22` -> `0.9.23` | [![age](https://badges.renovateapi.com/packages/maven/org.graalvm.buildtools:native-maven-plugin/0.9.23/age-slim)](https://docs.renovatebot.com/merge-confidence/) | [![adoption](https://badges.renovateapi.com/packages/maven/org.graalvm.buildtools:native-maven-plugin/0.9.23/adoption-slim)](https://docs.renovatebot.com/merge-confidence/) | [![passing](https://badges.renovateapi.com/packages/maven/org.graalvm.buildtools:native-maven-plugin/0.9.23/compatibility-slim/0.9.22)](https://docs.renovatebot.com/merge-confidence/) | [![confidence](https://badges.renovateapi.com/packages/maven/org.graalvm.buildtools:native-maven-plugin/0.9.23/confidence-slim/0.9.22)](https://docs.renovatebot.com/merge-confidence/) |

---

### Release Notes

<details>
<summary>graalvm/native-build-tools</summary>

### [`v0.9.23`](https://togithub.com/graalvm/native-build-tools/releases/tag/0.9.23)

[Compare Source](https://togithub.com/graalvm/native-build-tools/compare/0.9.22...0.9.23)

#### What's Changed

-   Fix "collect reachability metadata" with config cache by [@&#8203;melix](https://togithub.com/melix) in [https://github.com/graalvm/native-build-tools/pull/438](https://togithub.com/graalvm/native-build-tools/pull/438)
-   Remove use of GFileUtils by [@&#8203;melix](https://togithub.com/melix) in [https://github.com/graalvm/native-build-tools/pull/437](https://togithub.com/graalvm/native-build-tools/pull/437)
-   Display the GraalVM logo on the generated NBT docs by [@&#8203;gradinac](https://togithub.com/gradinac) in [https://github.com/graalvm/native-build-tools/pull/447](https://togithub.com/graalvm/native-build-tools/pull/447)

**Full Changelog**: https://github.com/graalvm/native-build-tools/compare/0.9.22...0.9.23

</details>

---

### Configuration

📅 **Schedule**: Branch creation - At any time (no schedule defined), Automerge - At any time (no schedule defined).

🚦 **Automerge**: Disabled by config. Please merge this manually once you are satisfied.

♻ **Rebasing**: Whenever PR becomes conflicted, or you tick the rebase/retry checkbox.

🔕 **Ignore**: Close this PR and you won't be reminded about this update again.

---

 - [ ] <!-- rebase-check -->If you want to rebase/retry this PR, check this box

---

This PR has been generated by [Mend Renovate](https://www.mend.io/free-developer-tools/renovate/). View repository job log [here](https://app.renovatebot.com/dashboard#github/googleapis/java-storage).
<!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiIzNS4xMTcuMyIsInVwZGF0ZWRJblZlciI6IjM1LjExNy4zIiwidGFyZ2V0QnJhbmNoIjoibWFpbiJ9-->



Patch:
diff --git a/.github/.OwlBot.lock.yaml b/.github/.OwlBot.lock.yaml
index aadf54f6..73568a1e 100644
--- a/.github/.OwlBot.lock.yaml
+++ b/.github/.OwlBot.lock.yaml
@@ -13,4 +13,5 @@
 # limitations under the License.
 docker:
   image: gcr.io/cloud-devrel-public-resources/owlbot-java:latest
-  digest: sha256:ad9cabee4c022f1aab04a71332369e0c23841062124818a4490f73337f790337
+  digest: sha256:af2eda87a54601ae7b7b2be5055c17b43ac98a7805b586772db314de8a7d4a1d
+# created: 2023-06-16T02:10:09.149325782Z
diff --git a/.github/dependabot.yml b/.github/dependabot.yml
index c8f413b0..fde1ced4 100644
--- a/.github/dependabot.yml
+++ b/.github/dependabot.yml
@@ -5,10 +5,13 @@ updates:
     schedule:
       interval: "daily"
     # Disable version updates for Maven dependencies
-    open-pull-requests-limit: 0
+    # we use renovate-bot as well as shared-dependencies BOM to update maven dependencies.
+    ignore: "*"
   - package-ecosystem: "pip"
     directory: "/"
     schedule:
       interval: "daily"
     # Disable version updates for pip dependencies
-    open-pull-requests-limit: 0
\ No newline at end of file
+    # If a security vulnerability comes in, we will be notified about 
+    # it via template in the synthtool repository.
+    ignore: "*"
diff --git a/google-cloud-storage/src/main/java/com/google/cloud/storage/Blob.java b/google-cloud-storage/src/main/java/com/google/cloud/storage/Blob.java
index 15d3c489..4bba8aef 100644
--- a/google-cloud-storage/src/main/java/com/google/cloud/storage/Blob.java
+++ b/google-cloud-storage/src/main/java/com/google/cloud/storage/Blob.java
@@ -40,6 +40,7 @@ import java.nio.file.Path;
 import java.security.Key;
 import java.time.OffsetDateTime;
 import java.util.Arrays;
+import java.util.Collection;
 import java.util.List;
 import java.util.Map;
 import java.util.Objects;
@@ -163,6 +164,40 @@ public class Blob extends BlobInfo {
       return new BlobSourceOption(UnifiedOpts.returnRawInputStream(shouldReturnRawInputStream));
     }
 
+    /**
+     * Deduplicate any options which are the same parameter. The value which comes last in {@code
+     * os} will be the value included in the return.
+     */
+    @BetaApi
+    public static BlobSourceOption[] dedupe(BlobSourceOption... os) {
+      return Option.dedupe(BlobSourceOption[]::new, os);
+    }
+
+    /**
+     * Deduplicate any options which are the same parameter.
+     *
+     * <p>The value which comes last in {@code collection} and {@code os} will be the value included
+     * in the return. All options from {@code os} will override their counterparts in {@code
+     * collection}.
+     */
+    @BetaApi
+    public static BlobSourceOption[] dedupe(
+        Collection<BlobSourceOption> collection, BlobSourceOption... os) {
+      return Option.dedupe(BlobSourceOption[]::new, collection, os);
+    }
+
+    /**
+     * Deduplicate any options which are the same parameter.
+     *
+     * <p>The value which comes last in {@code collection} and {@code os} will be the value included
+     * in the return. All options from {@code os} will override their counterparts in {@code
+     * collection}.
+     */
+    @BetaApi
+    public static BlobSourceOption[] dedupe(BlobSourceOption[] array, BlobSourceOption... os) {
+      return Option.dedupe(BlobSourceOption[]::new, array, os);
+    }
+
     static Storage.BlobSourceOption[] toSourceOptions(
         BlobInfo blobInfo, BlobSourceOption... options) {
       Storage.BlobSourceOption[] convertedOptions = new Storage.BlobSourceOption[options.length];
diff --git a/google-cloud-storage/src/main/java/com/google/cloud/storage/Bucket.java b/google-cloud-storage/src/main/java/com/google/cloud/storage/Bucket.java
index af5c2cc2..c8827c1f 100644
--- a/google-cloud-storage/src/main/java/com/google/cloud/storage/Bucket.java
+++ b/google-cloud-storage/src/main/java/com/google/cloud/storage/Bucket.java
@@ -30,7 +30,6 @@ import com.google.cloud.storage.UnifiedOpts.BucketOptExtractor;
 import com.google.cloud.storage.UnifiedOpts.BucketSourceOpt;
 import com.google.cloud.storage.UnifiedOpts.ObjectOptExtractor;
 import com.google.cloud.storage.UnifiedOpts.ObjectTargetOpt;
-import com.google.cloud.storage.UnifiedOpts.OptionShim;
 import com.google.common.collect.ImmutableList;
 import com.google.common.collect.Lists;
 import java.io.IOException;
@@ -41,6 +40,7 @@ import java.security.Key;
 import java.time.Duration;
 import java.time.OffsetDateTime;
 import java.util.Arrays;
+import java.util.Collection;
 import java.util.List;
 import java.util.Map;
 import java.util.Objects;
@@ -99,6 +99,41 @@ public class Bucket extends BucketInfo {
       return new BucketSourceOption(UnifiedOpts.userProject(userProject));
     }
 
+    /**
+     * Deduplicate any options which are the same parameter. The value which comes last in {@code
+     * os} will be the value included in the return.
+     */
+    @BetaApi
+    public static BucketSourceOption[] dedupe(BucketSourceOption... os) {
+      return Option.dedupe(BucketSourceOption[]::new, os);
+    }
+
+    /**
+     * Deduplicate any options which are the same parameter.
+     *
+     * <p>The value which comes last in {@code collection} and {@code os} will be the value included
+     * in the return. All options from {@code os} will override their counterparts in {@code
+     * collection}.
+     */
+    @BetaApi
+    public static BucketSourceOption[] dedupe(
+        Collection<BucketSourceOption> collection, BucketSourceOption... os) {
+      return Option.dedupe(BucketSourceOption[]::new, collection, os);
+    }
+
+    /**
+     * Deduplicate any options which are the same parameter.
+     *
+     * <p>The value which comes last in {@code collection} and {@code os} will be the value included
+     * in the return. All options from {@code os} will override their counterparts in {@code
+     * collection}.
+     */
+    @BetaApi
+    public static BucketSourceOption[] dedupe(
+        BucketSourceOption[] array, BucketSourceOption... os) {
+      return Option.dedupe(BucketSourceOption[]::new, array, os);
+    }
+
     static Storage.BucketSourceOption[] toSourceOptions(
         BucketInfo bucketInfo, BucketSourceOption... options) {
       Storage.BucketSourceOption[] convertedOptions =
@@ -237,6 +272,40 @@ public class Bucket extends BucketInfo {
       return new BlobTargetOption(UnifiedOpts.userProject(userProject));
     }
 
+    /**
+     * Deduplicate any options which are the same parameter. The value which comes last in {@code
+     * os} will be the value included in the return.
+     */
+    @BetaApi
+    public static BlobTargetOption[] dedupe(BlobTargetOption... os) {
+      return Option.dedupe(BlobTargetOption[]::new, os);
+    }
+
+    /**
+     * Deduplicate any options which are the same parameter.
+     *
+     * <p>The value which comes last in {@code collection} and {@code os} will be the value included
+     * in the return. All options from {@code os} will override their counterparts in {@code
+     * collection}.
+     */
+    @BetaApi
+    public static BlobTargetOption[] dedupe(
+        Collection<BlobTargetOption> collection, BlobTargetOption... os) {
+      return Option.dedupe(BlobTargetOption[]::new, collection, os);
+    }
+
+    /**
+     * Deduplicate any options which are the same parameter.
+     *
+     * <p>The value which comes last in {@code collection} and {@code os} will be the value included
+     * in the return. All options from {@code os} will override their counterparts in {@code
+     * collection}.
+     */
+    @BetaApi
+    public static BlobTargetOption[] dedupe(BlobTargetOption[] array, BlobTargetOption... os) {
+      return Option.dedupe(BlobTargetOption[]::new, array, os);
+    }
+
     static Storage.BlobTargetOption[] toTargetOptions(
         BlobInfo blobInfo, BlobTargetOption... options) {
       Storage.BlobTargetOption[] targetOptions = new Storage.BlobTargetOption[options.length];
@@ -255,7 +324,7 @@ public class Bucket extends BucketInfo {
   }
 
   /** Class for specifying blob write options when {@code Bucket} methods are used. */
-  public static class BlobWriteOption extends OptionShim<ObjectTargetOpt> implements Serializable {
+  public static class BlobWriteOption extends Option<ObjectTargetOpt> implements Serializable {
 
     private static final long serialVersionUID = 59762268190041584L;
 
@@ -366,6 +435,40 @@ public class Bucket extends BucketInfo {
       return new BlobWriteOption(UnifiedOpts.userProject(userProject));
     }
 
+    /**
+     * Deduplicate any options which are the same parameter. The value which comes last in {@code
+     * os} will be the value included in the return.
+     */
+    @BetaApi
+    public static BlobWriteOption[] dedupe(BlobWriteOption... os) {
+      return Option.dedupe(BlobWriteOption[]::new, os);
+    }
+
+    /**
+     * Deduplicate any options which are the same parameter.
+     *
+     * <p>The value which comes last in {@code collection} and {@code os} will be the value included
+     * in the return. All options from {@code os} will override their counterparts in {@code
+     * collection}.
+     */
+    @BetaApi
+    public static BlobWriteOption[] dedupe(
+        Collection<BlobWriteOption> collection, BlobWriteOption... os) {
+      return Option.dedupe(BlobWriteOption[]::new, collection, os);
+    }
+
+    /**
+     * Deduplicate any options which are the same parameter.
+     *
+     * <p>The value which comes last in {@code collection} and {@code os} will be the value included
+     * in the return. All options from {@code os} will override their counterparts in {@code
+     * collection}.
+     */
+    @BetaApi
+    public static BlobWriteOption[] dedupe(BlobWriteOption[] array, BlobWriteOption... os) {
+      return Option.dedupe(BlobWriteOption[]::new, array, os);
+    }
+
     static Storage.BlobWriteOption[] toWriteOptions(BlobInfo blobInfo, BlobWriteOption... options) {
       Storage.BlobWriteOption[] convertedOptions = new Storage.BlobWriteOption[options.length];
       for (int i = 0; i < options.length; i++) {
diff --git a/google-cloud-storage/src/main/java/com/google/cloud/storage/Option.java b/google-cloud-storage/src/main/java/com/google/cloud/storage/Option.java
index 5fb7479c..e526486e 100644
--- a/google-cloud-storage/src/main/java/com/google/cloud/storage/Option.java
+++ b/google-cloud-storage/src/main/java/com/google/cloud/storage/Option.java
@@ -18,6 +18,11 @@ package com.google.cloud.storage;
 
 import com.google.cloud.storage.UnifiedOpts.Opt;
 import java.io.Serializable;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.function.IntFunction;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
 
 /** Base class for Storage operation option. */
 @Deprecated
@@ -29,4 +34,32 @@ public abstract class Option<O extends Opt> extends UnifiedOpts.OptionShim<O>
   Option(O opt) {
     super(opt);
   }
+
+  @SafeVarargs
+  static <O extends Option<?>> O[] dedupe(IntFunction<O[]> gen, O... os) {
+    return dedupe(gen, Arrays.stream(os));
+  }
+
+  @SafeVarargs
+  static <O extends Option<?>> O[] dedupe(IntFunction<O[]> gen, Collection<O> collection, O... os) {
+    return dedupe(gen, Stream.of(collection.stream(), Arrays.stream(os)).flatMap(s -> s));
+  }
+
+  @SafeVarargs
+  static <O extends Option<?>> O[] dedupe(IntFunction<O[]> gen, O[] array, O... os) {
+    return dedupe(gen, Stream.of(Arrays.stream(array), Arrays.stream(os)).flatMap(s -> s));
+  }
+
+  /**
+   * All Options contain an {@link Opt}, {@code Opt}s are distinct classes allowing us to group
+   * based on those classes. Once grouped, we select the last element to provide last wins behavior.
+   *
+   * <p>Each of these helpers is an internal implementation detail, primarily due to the fact that
+   * generic arrays can not be instantiated in Java and requires a factory to be passed in.
+   */
+  private static <O extends Option<?>> O[] dedupe(IntFunction<O[]> gen, Stream<O> s) {
+    return s.collect(Collectors.groupingBy(o -> o.getOpt().getClass())).values().stream()
+        .map(l -> l.get(l.size() - 1))
+        .toArray(gen);
+  }
 }
diff --git a/google-cloud-storage/src/main/java/com/google/cloud/storage/Storage.java b/google-cloud-storage/src/main/java/com/google/cloud/storage/Storage.java
index d1838985..63a0793c 100644
--- a/google-cloud-storage/src/main/java/com/google/cloud/storage/Storage.java
+++ b/google-cloud-storage/src/main/java/com/google/cloud/storage/Storage.java
@@ -45,7 +45,6 @@ import com.google.cloud.storage.UnifiedOpts.NamedField;
 import com.google.cloud.storage.UnifiedOpts.ObjectListOpt;
 import com.google.cloud.storage.UnifiedOpts.ObjectSourceOpt;
 import com.google.cloud.storage.UnifiedOpts.ObjectTargetOpt;
-import com.google.cloud.storage.UnifiedOpts.OptionShim;
 import com.google.common.collect.ImmutableList;
 import com.google.common.collect.ImmutableSet;
 import com.google.common.collect.Iterables;
@@ -59,6 +58,7 @@ import java.net.URLConnection;
 import java.nio.file.Path;
 import java.security.Key;
 import java.util.Arrays;
+import java.util.Collection;
 import java.util.Collections;
 import java.util.LinkedHashSet;
 import java.util.LinkedList;
@@ -358,6 +358,39 @@ public interface Storage extends Service<StorageOptions>, AutoCloseable {
     public static BucketTargetOption projection(@NonNull String projection) {
       return new BucketTargetOption(UnifiedOpts.projection(projection));
     }
+
+    /**
+     * Deduplicate any options which are the same parameter. The value which comes last in {@code
+     * os} will be the value included in the return.
+     */
+    @BetaApi
+    public static BucketTargetOption[] dedupe(BucketTargetOption... os) {
+      return Option.dedupe(BucketTargetOption[]::new, os);
+    }
+
+    /**
+     * Deduplicate any options which are the same parameter.
+     *
+     * <p>The value which comes last in {@code collection} and {@code os} will be the value included
+     * in the return. All options from {@code os} will override their counterparts in {@code
+     * collection}.
+     */
+    public static BucketTargetOption[] dedupe(
+        Collection<BucketTargetOption> collection, BucketTargetOption... os) {
+      return Option.dedupe(BucketTargetOption[]::new, collection, os);
+    }
+
+    /**
+     * Deduplicate any options which are the same parameter.
+     *
+     * <p>The value which comes last in {@code collection} and {@code os} will be the value included
+     * in the return. All options from {@code os} will override their counterparts in {@code
+     * collection}.
+     */
+    public static BucketTargetOption[] dedupe(
+        BucketTargetOption[] array, BucketTargetOption... os) {
+      return Option.dedupe(BucketTargetOption[]::new, array, os);
+    }
   }
 
   /** Class for specifying bucket source options. */
@@ -400,6 +433,41 @@ public interface Storage extends Service<StorageOptions>, AutoCloseable {
     public static BucketSourceOption requestedPolicyVersion(long version) {
       return new BucketSourceOption(UnifiedOpts.requestedPolicyVersion(version));
     }
+
+    /**
+     * Deduplicate any options which are the same parameter. The value which comes last in {@code
+     * os} will be the value included in the return.
+     */
+    @BetaApi
+    public static BucketSourceOption[] dedupe(BucketSourceOption... os) {
+      return Option.dedupe(BucketSourceOption[]::new, os);
+    }
+
+    /**
+     * Deduplicate any options which are the same parameter.
+     *
+     * <p>The value which comes last in {@code collection} and {@code os} will be the value included
+     * in the return. All options from {@code os} will override their counterparts in {@code
+     * collection}.
+     */
+    @BetaApi
+    public static BucketSourceOption[] dedupe(
+        Collection<BucketSourceOption> collection, BucketSourceOption... os) {
+      return Option.dedupe(BucketSourceOption[]::new, collection, os);
+    }
+
+    /**
+     * Deduplicate any options which are the same parameter.
+     *
+     * <p>The value which comes last in {@code collection} and {@code os} will be the value included
+     * in the return. All options from {@code os} will override their counterparts in {@code
+     * collection}.
+     */
+    @BetaApi
+    public static BucketSourceOption[] dedupe(
+        BucketSourceOption[] array, BucketSourceOption... os) {
+      return Option.dedupe(BucketSourceOption[]::new, array, os);
+    }
   }
 
   /** Class for specifying listHmacKeys options */
@@ -456,6 +524,41 @@ public interface Storage extends Service<StorageOptions>, AutoCloseable {
     public static ListHmacKeysOption projectId(@NonNull String projectId) {
       return new ListHmacKeysOption(UnifiedOpts.projectId(projectId));
     }
+
+    /**
+     * Deduplicate any options which are the same parameter. The value which comes last in {@code
+     * os} will be the value included in the return.
+     */
+    @BetaApi
+    public static ListHmacKeysOption[] dedupe(ListHmacKeysOption... os) {
+      return Option.dedupe(ListHmacKeysOption[]::new, os);
+    }
+
+    /**
+     * Deduplicate any options which are the same parameter.
+     *
+     * <p>The value which comes last in {@code collection} and {@code os} will be the value included
+     * in the return. All options from {@code os} will override their counterparts in {@code
+     * collection}.
+     */
+    @BetaApi
+    public static ListHmacKeysOption[] dedupe(
+        Collection<ListHmacKeysOption> collection, ListHmacKeysOption... os) {
+      return Option.dedupe(ListHmacKeysOption[]::new, collection, os);
+    }
+
+    /**
+     * Deduplicate any options which are the same parameter.
+     *
+     * <p>The value which comes last in {@code collection} and {@code os} will be the value included
+     * in the return. All options from {@code os} will override their counterparts in {@code
+     * collection}.
+     */
+    @BetaApi
+    public static ListHmacKeysOption[] dedupe(
+        ListHmacKeysOption[] array, ListHmacKeysOption... os) {
+      return Option.dedupe(ListHmacKeysOption[]::new, array, os);
+    }
   }
 
   /** Class for specifying createHmacKey options */
@@ -482,6 +585,41 @@ public interface Storage extends Service<StorageOptions>, AutoCloseable {
     public static CreateHmacKeyOption projectId(@NonNull String projectId) {
       return new CreateHmacKeyOption(UnifiedOpts.projectId(projectId));
     }
+
+    /**
+     * Deduplicate any options which are the same parameter. The value which comes last in {@code
+     * os} will be the value included in the return.
+     */
+    @BetaApi
+    public static CreateHmacKeyOption[] dedupe(CreateHmacKeyOption... os) {
+      return Option.dedupe(CreateHmacKeyOption[]::new, os);
+    }
+
+    /**
+     * Deduplicate any options which are the same parameter.
+     *
+     * <p>The value which comes last in {@code collection} and {@code os} will be the value included
+     * in the return. All options from {@code os} will override their counterparts in {@code
+     * collection}.
+     */
+    @BetaApi
+    public static CreateHmacKeyOption[] dedupe(
+        Collection<CreateHmacKeyOption> collection, CreateHmacKeyOption... os) {
+      return Option.dedupe(CreateHmacKeyOption[]::new, collection, os);
+    }
+
+    /**
+     * Deduplicate any options which are the same parameter.
+     *
+     * <p>The value which comes last in {@code collection} and {@code os} will be the value included
+     * in the return. All options from {@code os} will override their counterparts in {@code
+     * collection}.
+     */
+    @BetaApi
+    public static CreateHmacKeyOption[] dedupe(
+        CreateHmacKeyOption[] array, CreateHmacKeyOption... os) {
+      return Option.dedupe(CreateHmacKeyOption[]::new, array, os);
+    }
   }
 
   /** Class for specifying getHmacKey options */
@@ -507,6 +645,40 @@ public interface Storage extends Service<StorageOptions>, AutoCloseable {
     public static GetHmacKeyOption projectId(@NonNull String projectId) {
       return new GetHmacKeyOption(UnifiedOpts.projectId(projectId));
     }
+
+    /**
+     * Deduplicate any options which are the same parameter. The value which comes last in {@code
+     * os} will be the value included in the return.
+     */
+    @BetaApi
+    public static GetHmacKeyOption[] dedupe(GetHmacKeyOption... os) {
+      return Option.dedupe(GetHmacKeyOption[]::new, os);
+    }
+
+    /**
+     * Deduplicate any options which are the same parameter.
+     *
+     * <p>The value which comes last in {@code collection} and {@code os} will be the value included
+     * in the return. All options from {@code os} will override their counterparts in {@code
+     * collection}.
+     */
+    @BetaApi
+    public static GetHmacKeyOption[] dedupe(
+        Collection<GetHmacKeyOption> collection, GetHmacKeyOption... os) {
+      return Option.dedupe(GetHmacKeyOption[]::new, collection, os);
+    }
+
+    /**
+     * Deduplicate any options which are the same parameter.
+     *
+     * <p>The value which comes last in {@code collection} and {@code os} will be the value included
+     * in the return. All options from {@code os} will override their counterparts in {@code
+     * collection}.
+     */
+    @BetaApi
+    public static GetHmacKeyOption[] dedupe(GetHmacKeyOption[] array, GetHmacKeyOption... os) {
+      return Option.dedupe(GetHmacKeyOption[]::new, array, os);
+    }
   }
 
   /** Class for specifying deleteHmacKey options */
@@ -523,6 +695,41 @@ public interface Storage extends Service<StorageOptions>, AutoCloseable {
     public static DeleteHmacKeyOption userProject(@NonNull String userProject) {
       return new DeleteHmacKeyOption(UnifiedOpts.userProject(userProject));
     }
+
+    /**
+     * Deduplicate any options which are the same parameter. The value which comes last in {@code
+     * os} will be the value included in the return.
+     */
+    @BetaApi
+    public static DeleteHmacKeyOption[] dedupe(DeleteHmacKeyOption... os) {
+      return Option.dedupe(DeleteHmacKeyOption[]::new, os);
+    }
+
+    /**
+     * Deduplicate any options which are the same parameter.
+     *
+     * <p>The value which comes last in {@code collection} and {@code os} will be the value included
+     * in the return. All options from {@code os} will override their counterparts in {@code
+     * collection}.
+     */
+    @BetaApi
+    public static DeleteHmacKeyOption[] dedupe(
+        Collection<DeleteHmacKeyOption> collection, DeleteHmacKeyOption... os) {
+      return Option.dedupe(DeleteHmacKeyOption[]::new, collection, os);
+    }
+
+    /**
+     * Deduplicate any options which are the same parameter.
+     *
+     * <p>The value which comes last in {@code collection} and {@code os} will be the value included
+     * in the return. All options from {@code os} will override their counterparts in {@code
+     * collection}.
+     */
+    @BetaApi
+    public static DeleteHmacKeyOption[] dedupe(
+        DeleteHmacKeyOption[] array, DeleteHmacKeyOption... os) {
+      return Option.dedupe(DeleteHmacKeyOption[]::new, array, os);
+    }
   }
 
   /** Class for specifying updateHmacKey options */
@@ -539,6 +746,41 @@ public interface Storage extends Service<StorageOptions>, AutoCloseable {
     public static UpdateHmacKeyOption userProject(@NonNull String userProject) {
       return new UpdateHmacKeyOption(UnifiedOpts.userProject(userProject));
     }
+
+    /**
+     * Deduplicate any options which are the same parameter. The value which comes last in {@code
+     * os} will be the value included in the return.
+     */
+    @BetaApi
+    public static UpdateHmacKeyOption[] dedupe(UpdateHmacKeyOption... os) {
+      return Option.dedupe(UpdateHmacKeyOption[]::new, os);
+    }
+
+    /**
+     * Deduplicate any options which are the same parameter.
+     *
+     * <p>The value which comes last in {@code collection} and {@code os} will be the value included
+     * in the return. All options from {@code os} will override their counterparts in {@code
+     * collection}.
+     */
+    @BetaApi
+    public static UpdateHmacKeyOption[] dedupe(
+        Collection<UpdateHmacKeyOption> collection, UpdateHmacKeyOption... os) {
+      return Option.dedupe(UpdateHmacKeyOption[]::new, collection, os);
+    }
+
+    /**
+     * Deduplicate any options which are the same parameter.
+     *
+     * <p>The value which comes last in {@code collection} and {@code os} will be the value included
+     * in the return. All options from {@code os} will override their counterparts in {@code
+     * collection}.
+     */
+    @BetaApi
+    public static UpdateHmacKeyOption[] dedupe(
+        UpdateHmacKeyOption[] array, UpdateHmacKeyOption... os) {
+      return Option.dedupe(UpdateHmacKeyOption[]::new, array, os);
+    }
   }
 
   /** Class for specifying bucket get options. */
@@ -593,6 +835,40 @@ public interface Storage extends Service<StorageOptions>, AutoCloseable {
               .build();
       return new BucketGetOption(UnifiedOpts.fields(set));
     }
+
+    /**
+     * Deduplicate any options which are the same parameter. The value which comes last in {@code
+     * os} will be the value included in the return.
+     */
+    @BetaApi
+    public static BucketGetOption[] dedupe(BucketGetOption... os) {
+      return Option.dedupe(BucketGetOption[]::new, os);
+    }
+
+    /**
+     * Deduplicate any options which are the same parameter.
+     *
+     * <p>The value which comes last in {@code collection} and {@code os} will be the value included
+     * in the return. All options from {@code os} will override their counterparts in {@code
+     * collection}.
+     */
+    @BetaApi
+    public static BucketGetOption[] dedupe(
+        Collection<BucketGetOption> collection, BucketGetOption... os) {
+      return Option.dedupe(BucketGetOption[]::new, collection, os);
+    }
+
+    /**
+     * Deduplicate any options which are the same parameter.
+     *
+     * <p>The value which comes last in {@code collection} and {@code os} will be the value included
+     * in the return. All options from {@code os} will override their counterparts in {@code
+     * collection}.
+     */
+    @BetaApi
+    public static BucketGetOption[] dedupe(BucketGetOption[] array, BucketGetOption... os) {
+      return Option.dedupe(BucketGetOption[]::new, array, os);
+    }
   }
 
   /** Class for specifying blob target options. */
@@ -743,10 +1019,44 @@ public interface Storage extends Service<StorageOptions>, AutoCloseable {
     public static BlobTargetOption kmsKeyName(@NonNull String kmsKeyName) {
       return new BlobTargetOption(UnifiedOpts.kmsKeyName(kmsKeyName));
     }
+
+    /**
+     * Deduplicate any options which are the same parameter. The value which comes last in {@code
+     * os} will be the value included in the return.
+     */
+    @BetaApi
+    public static BlobTargetOption[] dedupe(BlobTargetOption... os) {
+      return Option.dedupe(BlobTargetOption[]::new, os);
+    }
+
+    /**
+     * Deduplicate any options which are the same parameter.
+     *
+     * <p>The value which comes last in {@code collection} and {@code os} will be the value included
+     * in the return. All options from {@code os} will override their counterparts in {@code
+     * collection}.
+     */
+    @BetaApi
+    public static BlobTargetOption[] dedupe(
+        Collection<BlobTargetOption> collection, BlobTargetOption... os) {
+      return Option.dedupe(BlobTargetOption[]::new, collection, os);
+    }
+
+    /**
+     * Deduplicate any options which are the same parameter.
+     *
+     * <p>The value which comes last in {@code collection} and {@code os} will be the value included
+     * in the return. All options from {@code os} will override their counterparts in {@code
+     * collection}.
+     */
+    @BetaApi
+    public static BlobTargetOption[] dedupe(BlobTargetOption[] array, BlobTargetOption... os) {
+      return Option.dedupe(BlobTargetOption[]::new, array, os);
+    }
   }
 
   /** Class for specifying blob write options. */
-  class BlobWriteOption extends OptionShim<ObjectTargetOpt> implements Serializable {
+  class BlobWriteOption extends Option<ObjectTargetOpt> implements Serializable {
 
     private static final long serialVersionUID = 5536338021856320475L;
 
@@ -921,6 +1231,40 @@ public interface Storage extends Service<StorageOptions>, AutoCloseable {
     public static BlobWriteOption detectContentType() {
       return new BlobWriteOption(UnifiedOpts.detectContentType());
     }
+
+    /**
+     * Deduplicate any options which are the same parameter. The value which comes last in {@code
+     * os} will be the value included in the return.
+     */
+    @BetaApi
+    public static BlobWriteOption[] dedupe(BlobWriteOption... os) {
+      return Option.dedupe(BlobWriteOption[]::new, os);
+    }
+
+    /**
+     * Deduplicate any options which are the same parameter.
+     *
+     * <p>The value which comes last in {@code collection} and {@code os} will be the value included
+     * in the return. All options from {@code os} will override their counterparts in {@code
+     * collection}.
+     */
+    @BetaApi
+    public static BlobWriteOption[] dedupe(
+        Collection<BlobWriteOption> collection, BlobWriteOption... os) {
+      return Option.dedupe(BlobWriteOption[]::new, collection, os);
+    }
+
+    /**
+     * Deduplicate any options which are the same parameter.
+     *
+     * <p>The value which comes last in {@code collection} and {@code os} will be the value included
+     * in the return. All options from {@code os} will override their counterparts in {@code
+     * collection}.
+     */
+    @BetaApi
+    public static BlobWriteOption[] dedupe(BlobWriteOption[] array, BlobWriteOption... os) {
+      return Option.dedupe(BlobWriteOption[]::new, array, os);
+    }
   }
 
   /** Class for specifying blob source options. */
@@ -1034,6 +1378,40 @@ public interface Storage extends Service<StorageOptions>, AutoCloseable {
     public static BlobSourceOption shouldReturnRawInputStream(boolean shouldReturnRawInputStream) {
       return new BlobSourceOption(UnifiedOpts.returnRawInputStream(shouldReturnRawInputStream));
     }
+
+    /**
+     * Deduplicate any options which are the same parameter. The value which comes last in {@code
+     * os} will be the value included in the return.
+     */
+    @BetaApi
+    public static BlobSourceOption[] dedupe(BlobSourceOption... os) {
+      return Option.dedupe(BlobSourceOption[]::new, os);
+    }
+
+    /**
+     * Deduplicate any options which are the same parameter.
+     *
+     * <p>The value which comes last in {@code collection} and {@code os} will be the value included
+     * in the return. All options from {@code os} will override their counterparts in {@code
+     * collection}.
+     */
+    @BetaApi
+    public static BlobSourceOption[] dedupe(
+        Collection<BlobSourceOption> collection, BlobSourceOption... os) {
+      return Option.dedupe(BlobSourceOption[]::new, collection, os);
+    }
+
+    /**
+     * Deduplicate any options which are the same parameter.
+     *
+     * <p>The value which comes last in {@code collection} and {@code os} will be the value included
+     * in the return. All options from {@code os} will override their counterparts in {@code
+     * collection}.
+     */
+    @BetaApi
+    public static BlobSourceOption[] dedupe(BlobSourceOption[] array, BlobSourceOption... os) {
+      return Option.dedupe(BlobSourceOption[]::new, array, os);
+    }
   }
 
   /** Class for specifying blob get options. */
@@ -1161,6 +1539,40 @@ public interface Storage extends Service<StorageOptions>, AutoCloseable {
     public static BlobGetOption shouldReturnRawInputStream(boolean shouldReturnRawInputStream) {
       return new BlobGetOption(UnifiedOpts.returnRawInputStream(shouldReturnRawInputStream));
     }
+
+    /**
+     * Deduplicate any options which are the same parameter. The value which comes last in {@code
+     * os} will be the value included in the return.
+     */
+    @BetaApi
+    public static BlobGetOption[] dedupe(BlobGetOption... os) {
+      return Option.dedupe(BlobGetOption[]::new, os);
+    }
+
+    /**
+     * Deduplicate any options which are the same parameter.
+     *
+     * <p>The value which comes last in {@code collection} and {@code os} will be the value included
+     * in the return. All options from {@code os} will override their counterparts in {@code
+     * collection}.
+     */
+    @BetaApi
+    public static BlobGetOption[] dedupe(
+        Collection<BlobGetOption> collection, BlobGetOption... os) {
+      return Option.dedupe(BlobGetOption[]::new, collection, os);
+    }
+
+    /**
+     * Deduplicate any options which are the same parameter.
+     *
+     * <p>The value which comes last in {@code collection} and {@code os} will be the value included
+     * in the return. All options from {@code os} will override their counterparts in {@code
+     * collection}.
+     */
+    @BetaApi
+    public static BlobGetOption[] dedupe(BlobGetOption[] array, BlobGetOption... os) {
+      return Option.dedupe(BlobGetOption[]::new, array, os);
+    }
   }
 
   /** Class for specifying bucket list options. */
@@ -1219,6 +1631,40 @@ public interface Storage extends Service<StorageOptions>, AutoCloseable {
               .collect(ImmutableSet.toImmutableSet());
       return new BucketListOption(UnifiedOpts.fields(set));
     }
+
+    /**
+     * Deduplicate any options which are the same parameter. The value which comes last in {@code
+     * os} will be the value included in the return.
+     */
+    @BetaApi
+    public static BucketListOption[] dedupe(BucketListOption... os) {
+      return Option.dedupe(BucketListOption[]::new, os);
+    }
+
+    /**
+     * Deduplicate any options which are the same parameter.
+     *
+     * <p>The value which comes last in {@code collection} and {@code os} will be the value included
+     * in the return. All options from {@code os} will override their counterparts in {@code
+     * collection}.
+     */
+    @BetaApi
+    public static BucketListOption[] dedupe(
+        Collection<BucketListOption> collection, BucketListOption... os) {
+      return Option.dedupe(BucketListOption[]::new, collection, os);
+    }
+
+    /**
+     * Deduplicate any options which are the same parameter.
+     *
+     * <p>The value which comes last in {@code collection} and {@code os} will be the value included
+     * in the return. All options from {@code os} will override their counterparts in {@code
+     * collection}.
+     */
+    @BetaApi
+    public static BucketListOption[] dedupe(BucketListOption[] array, BucketListOption... os) {
+      return Option.dedupe(BucketListOption[]::new, array, os);
+    }
   }
 
   /** Class for specifying blob list options. */
@@ -1351,6 +1797,40 @@ public interface Storage extends Service<StorageOptions>, AutoCloseable {
               .collect(ImmutableSet.toImmutableSet());
       return new BlobListOption(UnifiedOpts.fields(set));
     }
+
+    /**
+     * Deduplicate any options which are the same parameter. The value which comes last in {@code
+     * os} will be the value included in the return.
+     */
+    @BetaApi
+    public static BlobListOption[] dedupe(BlobListOption... os) {
+      return Option.dedupe(BlobListOption[]::new, os);
+    }
+
+    /**
+     * Deduplicate any options which are the same parameter.
+     *
+     * <p>The value which comes last in {@code collection} and {@code os} will be the value included
+     * in the return. All options from {@code os} will override their counterparts in {@code
+     * collection}.
+     */
+    @BetaApi
+    public static BlobListOption[] dedupe(
+        Collection<BlobListOption> collection, BlobListOption... os) {
+      return Option.dedupe(BlobListOption[]::new, collection, os);
+    }
+
+    /**
+     * Deduplicate any options which are the same parameter.
+     *
+     * <p>The value which comes last in {@code collection} and {@code os} will be the value included
+     * in the return. All options from {@code os} will override their counterparts in {@code
+     * collection}.
+     */
+    @BetaApi
+    public static BlobListOption[] dedupe(BlobListOption[] array, BlobListOption... os) {
+      return Option.dedupe(BlobListOption[]::new, array, os);
+    }
   }
 
   /** Class for specifying Post Policy V4 options. * */
diff --git a/samples/native-image-sample/pom.xml b/samples/native-image-sample/pom.xml
index af1943d5..14152b6c 100644
--- a/samples/native-image-sample/pom.xml
+++ b/samples/native-image-sample/pom.xml
@@ -134,7 +134,7 @@
           <plugin>
             <groupId>org.graalvm.buildtools</groupId>
             <artifactId>native-maven-plugin</artifactId>
-            <version>0.9.22</version>
+            <version>0.9.23</version>
             <extensions>true</extensions>
             <configuration>
               <mainClass>com.example.storage.NativeImageStorageSample</mainClass>


Test patch:
diff --git a/google-cloud-storage/src/test/java/com/google/cloud/storage/it/DedupeOptionTest.java b/google-cloud-storage/src/test/java/com/google/cloud/storage/it/DedupeOptionTest.java
new file mode 100644
index 00000000..b7b38df0
--- /dev/null
+++ b/google-cloud-storage/src/test/java/com/google/cloud/storage/it/DedupeOptionTest.java
@@ -0,0 +1,163 @@
+/*
+ * Copyright 2023 Google LLC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *       http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.google.cloud.storage.it;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import com.google.cloud.storage.Blob;
+import com.google.cloud.storage.Bucket;
+import com.google.cloud.storage.Storage;
+import com.google.cloud.storage.Storage.BucketTargetOption;
+import com.google.common.collect.ImmutableList;
+import org.junit.Test;
+
+public final class DedupeOptionTest {
+
+  @Test
+  public void dedupe_varargs() {
+    BucketTargetOption[] dedupe =
+        BucketTargetOption.dedupe(
+            BucketTargetOption.userProject("abc"),
+            BucketTargetOption.metagenerationMatch(),
+            BucketTargetOption.userProject("xyz"));
+
+    assertThat(dedupe)
+        .asList()
+        .containsExactly(
+            BucketTargetOption.metagenerationMatch(), BucketTargetOption.userProject("xyz"));
+  }
+
+  @Test
+  public void dedupe_collection_varargs() {
+    BucketTargetOption[] dedupe =
+        BucketTargetOption.dedupe(
+            ImmutableList.of(
+                BucketTargetOption.userProject("abc"), BucketTargetOption.metagenerationMatch()),
+            BucketTargetOption.userProject("xyz"));
+
+    assertThat(dedupe)
+        .asList()
+        .containsExactly(
+            BucketTargetOption.metagenerationMatch(), BucketTargetOption.userProject("xyz"));
+  }
+
+  @Test
+  public void dedupe_array_varargs() {
+    BucketTargetOption[] dedupe =
+        BucketTargetOption.dedupe(
+            new BucketTargetOption[] {
+              BucketTargetOption.userProject("abc"), BucketTargetOption.metagenerationMatch()
+            },
+            BucketTargetOption.userProject("xyz"));
+
+    assertThat(dedupe)
+        .asList()
+        .containsExactly(
+            BucketTargetOption.metagenerationMatch(), BucketTargetOption.userProject("xyz"));
+  }
+
+  @Test
+  public void allClasses_varargs() {
+    Storage.BlobGetOption.dedupe();
+    Storage.BlobListOption.dedupe();
+    Storage.BlobSourceOption.dedupe();
+    Storage.BlobTargetOption.dedupe();
+    Storage.BlobWriteOption.dedupe();
+    Storage.BucketGetOption.dedupe();
+    Storage.BucketListOption.dedupe();
+    Storage.BucketSourceOption.dedupe();
+    Storage.BucketTargetOption.dedupe();
+    Storage.CreateHmacKeyOption.dedupe();
+    Storage.DeleteHmacKeyOption.dedupe();
+    Storage.GetHmacKeyOption.dedupe();
+    Storage.ListHmacKeysOption.dedupe();
+    Storage.UpdateHmacKeyOption.dedupe();
+
+    Bucket.BlobTargetOption.dedupe();
+    Bucket.BlobWriteOption.dedupe();
+    Bucket.BucketSourceOption.dedupe();
+
+    Blob.BlobSourceOption.dedupe();
+  }
+
+  @Test
+  public void allClasses_collection_varargs() {
+    Storage.BlobGetOption.dedupe(ImmutableList.of());
+    Storage.BlobListOption.dedupe(ImmutableList.of());
+    Storage.BlobSourceOption.dedupe(ImmutableList.of());
+    Storage.BlobTargetOption.dedupe(ImmutableList.of());
+    Storage.BlobWriteOption.dedupe(ImmutableList.of());
+    Storage.BucketGetOption.dedupe(ImmutableList.of());
+    Storage.BucketListOption.dedupe(ImmutableList.of());
+    Storage.BucketSourceOption.dedupe(ImmutableList.of());
+    Storage.BucketTargetOption.dedupe(ImmutableList.of());
+    Storage.CreateHmacKeyOption.dedupe(ImmutableList.of());
+    Storage.DeleteHmacKeyOption.dedupe(ImmutableList.of());
+    Storage.GetHmacKeyOption.dedupe(ImmutableList.of());
+    Storage.ListHmacKeysOption.dedupe(ImmutableList.of());
+    Storage.UpdateHmacKeyOption.dedupe(ImmutableList.of());
+
+    Bucket.BlobTargetOption.dedupe(ImmutableList.of());
+    Bucket.BlobWriteOption.dedupe(ImmutableList.of());
+    Bucket.BucketSourceOption.dedupe(ImmutableList.of());
+
+    Blob.BlobSourceOption.dedupe(ImmutableList.of());
+  }
+
+  @Test
+  public void allClasses_array_varargs() {
+    String p = "proj";
+    Storage.BlobGetOption.dedupe(
+        new Storage.BlobGetOption[0], Storage.BlobGetOption.userProject(p));
+    Storage.BlobListOption.dedupe(
+        new Storage.BlobListOption[0], Storage.BlobListOption.userProject(p));
+    Storage.BlobSourceOption.dedupe(
+        new Storage.BlobSourceOption[0], Storage.BlobSourceOption.userProject(p));
+    Storage.BlobTargetOption.dedupe(
+        new Storage.BlobTargetOption[0], Storage.BlobTargetOption.userProject(p));
+    Storage.BlobWriteOption.dedupe(
+        new Storage.BlobWriteOption[0], Storage.BlobWriteOption.userProject(p));
+    Storage.BucketGetOption.dedupe(
+        new Storage.BucketGetOption[0], Storage.BucketGetOption.userProject(p));
+    Storage.BucketListOption.dedupe(
+        new Storage.BucketListOption[0], Storage.BucketListOption.userProject(p));
+    Storage.BucketSourceOption.dedupe(
+        new Storage.BucketSourceOption[0], Storage.BucketSourceOption.userProject(p));
+    Storage.BucketTargetOption.dedupe(
+        new Storage.BucketTargetOption[0], Storage.BucketTargetOption.userProject(p));
+    Storage.CreateHmacKeyOption.dedupe(
+        new Storage.CreateHmacKeyOption[0], Storage.CreateHmacKeyOption.userProject(p));
+    Storage.DeleteHmacKeyOption.dedupe(
+        new Storage.DeleteHmacKeyOption[0], Storage.DeleteHmacKeyOption.userProject(p));
+    Storage.GetHmacKeyOption.dedupe(
+        new Storage.GetHmacKeyOption[0], Storage.GetHmacKeyOption.userProject(p));
+    Storage.ListHmacKeysOption.dedupe(
+        new Storage.ListHmacKeysOption[0], Storage.ListHmacKeysOption.userProject(p));
+    Storage.UpdateHmacKeyOption.dedupe(
+        new Storage.UpdateHmacKeyOption[0], Storage.UpdateHmacKeyOption.userProject(p));
+
+    Bucket.BlobTargetOption.dedupe(
+        new Bucket.BlobTargetOption[0], Bucket.BlobTargetOption.userProject(p));
+    Bucket.BlobWriteOption.dedupe(
+        new Bucket.BlobWriteOption[0], Bucket.BlobWriteOption.userProject(p));
+    Bucket.BucketSourceOption.dedupe(
+        new Bucket.BucketSourceOption[0], Bucket.BucketSourceOption.userProject(p));
+
+    Blob.BlobSourceOption.dedupe(
+        new Blob.BlobSourceOption[0], Blob.BlobSourceOption.userProject(p));
+  }
+}