• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

grpc / grpc-java / #20032

29 Oct 2025 08:58AM UTC coverage: 88.561% (-0.03%) from 88.586%
#20032

push

github

web-flow
api: Fix name resolver bridge listener handling for address resolution errors (#12441)

Fixes https://github.com/grpc/grpc-java/issues/12444

This PR addresses a bug in the `NameResolver.Listener` to
`NameResolver.Listener2` bridge affecting custom NameResolver
implementations using Listener.

The bridge in `NameResolver.start(Listener)` at
https://github.com/grpc/grpc-java/blob/master/api/src/main/java/io/grpc/NameResolver.java#L100
unconditionally calls `getValue()` on the `StatusOr`, throwing
`java.lang.IllegalStateException: No value present.` when the result
contains an error.

This was identified when upgrading from gRPC `v1.63.3` to `v1.75.0`. 

The bug occurs due to `DnsNameResolver`'s error handling changes between
versions:

- `v1.63.3`: Errors reported via `Listener.onError()`
(https://github.com/grpc/grpc-java/blob/v1.63.x/core/src/main/java/io/grpc/internal/DnsNameResolver.java#L319)
- `v1.75.0`: Errors passed via `Listener2.onResult2()` with a
ResolutionResult containing either addresses OR an error
(https://github.com/grpc/grpc-java/blob/master/core/src/main/java/io/grpc/internal/DnsNameResolver.java#L322)

This PR updates the bridge to check whether `ResolutionResult` contains
addresses or an error. It passes the error via `onError` and addresses
via `onAddresses`.

**Reproducing the Issue**
The `startOnOldListener_resolverReportsError` test reproduces a similar
issue. It creates a custom `NameResolver` that reports errors through
the `ResolutionResult` like the `DNSNameResolver` in `v1.75.0`, passes
an old Listener to `resolver.start`, which triggers the bridge code
path.

Without the fix, the bridge calls `getValue()` on the error containing
`StatusOr`, throwing `IllegalStateException: No value present`.

With the fix, the bridge checks `hasValue()` first and correctly routes
to `listener.onError()` when appropriate. This ensures backward
compatibility for `Listener` implementations when resolvers report
errors via `ResolutionResult`.

34941 of 39454 relevant lines covered (88.56%)

0.89 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

79.37
/../api/src/main/java/io/grpc/NameResolver.java
1
/*
2
 * Copyright 2015 The gRPC Authors
3
 *
4
 * Licensed under the Apache License, Version 2.0 (the "License");
5
 * you may not use this file except in compliance with the License.
6
 * You may obtain a copy of the License at
7
 *
8
 *     http://www.apache.org/licenses/LICENSE-2.0
9
 *
10
 * Unless required by applicable law or agreed to in writing, software
11
 * distributed under the License is distributed on an "AS IS" BASIS,
12
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
 * See the License for the specific language governing permissions and
14
 * limitations under the License.
15
 */
16

17
package io.grpc;
18

19
import static com.google.common.base.Preconditions.checkArgument;
20
import static com.google.common.base.Preconditions.checkNotNull;
21

22
import com.google.common.base.MoreObjects;
23
import com.google.common.base.MoreObjects.ToStringHelper;
24
import com.google.common.base.Objects;
25
import com.google.errorprone.annotations.InlineMe;
26
import java.lang.annotation.Documented;
27
import java.lang.annotation.Retention;
28
import java.lang.annotation.RetentionPolicy;
29
import java.net.URI;
30
import java.util.Collections;
31
import java.util.IdentityHashMap;
32
import java.util.List;
33
import java.util.Map;
34
import java.util.concurrent.Executor;
35
import java.util.concurrent.ScheduledExecutorService;
36
import javax.annotation.Nullable;
37
import javax.annotation.concurrent.Immutable;
38
import javax.annotation.concurrent.ThreadSafe;
39

40
/**
41
 * A pluggable component that resolves a target {@link URI} and return addresses to the caller.
42
 *
43
 * <p>A {@code NameResolver} uses the URI's scheme to determine whether it can resolve it, and uses
44
 * the components after the scheme for actual resolution.
45
 *
46
 * <p>The addresses and attributes of a target may be changed over time, thus the caller registers a
47
 * {@link Listener} to receive continuous updates.
48
 *
49
 * <p>A {@code NameResolver} does not need to automatically re-resolve on failure. Instead, the
50
 * {@link Listener} is responsible for eventually (after an appropriate backoff period) invoking
51
 * {@link #refresh()}.
52
 *
53
 * <p>Implementations <strong>don't need to be thread-safe</strong>.  All methods are guaranteed to
54
 * be called sequentially.  Additionally, all methods that have side-effects, i.e.,
55
 * {@link #start(Listener2)}, {@link #shutdown} and {@link #refresh} are called from the same
56
 * {@link SynchronizationContext} as returned by {@link Args#getSynchronizationContext}. <strong>Do
57
 * not block</strong> within the synchronization context; blocking I/O and time-consuming tasks
58
 * should be offloaded to a separate thread, generally {@link Args#getOffloadExecutor}.
59
 *
60
 * @since 1.0.0
61
 */
62
@ExperimentalApi("https://github.com/grpc/grpc-java/issues/1770")
63
public abstract class NameResolver {
1✔
64
  /**
65
   * Returns the authority used to authenticate connections to servers.  It <strong>must</strong> be
66
   * from a trusted source, because if the authority is tampered with, RPCs may be sent to the
67
   * attackers which may leak sensitive user data.
68
   *
69
   * <p>An implementation must generate it without blocking, typically in line, and
70
   * <strong>must</strong> keep it unchanged. {@code NameResolver}s created from the same factory
71
   * with the same argument must return the same authority.
72
   *
73
   * @since 1.0.0
74
   */
75
  public abstract String getServiceAuthority();
76

77
  /**
78
   * Starts the resolution. The method is not supposed to throw any exceptions. That might cause the
79
   * Channel that the name resolver is serving to crash. Errors should be propagated
80
   * through {@link Listener#onError}.
81
   * 
82
   * <p>An instance may not be started more than once, by any overload of this method, even after
83
   * an intervening call to {@link #shutdown}.
84
   *
85
   * @param listener used to receive updates on the target
86
   * @since 1.0.0
87
   */
88
  public void start(final Listener listener) {
89
    if (listener instanceof Listener2) {
1✔
90
      start((Listener2) listener);
×
91
    } else {
92
      start(new Listener2() {
1✔
93
          @Override
94
          public void onError(Status error) {
95
            listener.onError(error);
1✔
96
          }
1✔
97

98
          @Override
99
          public void onResult(ResolutionResult resolutionResult) {
100
            StatusOr<List<EquivalentAddressGroup>> addressesOrError =
1✔
101
                resolutionResult.getAddressesOrError();
1✔
102
            if (addressesOrError.hasValue()) {
1✔
103
              listener.onAddresses(addressesOrError.getValue(),
1✔
104
                  resolutionResult.getAttributes());
1✔
105
            } else {
106
              listener.onError(addressesOrError.getStatus());
1✔
107
            }
108
          }
1✔
109
      });
110
    }
111
  }
1✔
112

113
  /**
114
   * Starts the resolution. The method is not supposed to throw any exceptions. That might cause the
115
   * Channel that the name resolver is serving to crash. Errors should be propagated
116
   * through {@link Listener2#onError}.
117
   * 
118
   * <p>An instance may not be started more than once, by any overload of this method, even after
119
   * an intervening call to {@link #shutdown}.
120
   *
121
   * @param listener used to receive updates on the target
122
   * @since 1.21.0
123
   */
124
  public void start(Listener2 listener) {
125
    start((Listener) listener);
×
126
  }
×
127

128
  /**
129
   * Stops the resolution. Updates to the Listener will stop.
130
   *
131
   * @since 1.0.0
132
   */
133
  public abstract void shutdown();
134

135
  /**
136
   * Re-resolve the name.
137
   *
138
   * <p>Can only be called after {@link #start} has been called.
139
   *
140
   * <p>This is only a hint. Implementation takes it as a signal but may not start resolution
141
   * immediately. It should never throw.
142
   *
143
   * <p>The default implementation is no-op.
144
   *
145
   * @since 1.0.0
146
   */
147
  public void refresh() {}
1✔
148

149
  /**
150
   * Factory that creates {@link NameResolver} instances.
151
   *
152
   * @since 1.0.0
153
   */
154
  @ExperimentalApi("https://github.com/grpc/grpc-java/issues/1770")
155
  public abstract static class Factory {
1✔
156
    /**
157
     * Creates a {@link NameResolver} for the given target URI, or {@code null} if the given URI
158
     * cannot be resolved by this factory. The decision should be solely based on the scheme of the
159
     * URI.
160
     *
161
     * @param targetUri the target URI to be resolved, whose scheme must not be {@code null}
162
     * @param args other information that may be useful
163
     *
164
     * @since 1.21.0
165
     */
166
    public abstract NameResolver newNameResolver(URI targetUri, final Args args);
167

168
    /**
169
     * Returns the default scheme, which will be used to construct a URI when {@link
170
     * ManagedChannelBuilder#forTarget(String)} is given an authority string instead of a compliant
171
     * URI.
172
     *
173
     * @since 1.0.0
174
     */
175
    public abstract String getDefaultScheme();
176
  }
177

178
  /**
179
   * Receives address updates.
180
   *
181
   * <p>All methods are expected to return quickly.
182
   *
183
   * @since 1.0.0
184
   */
185
  @ExperimentalApi("https://github.com/grpc/grpc-java/issues/1770")
186
  @ThreadSafe
187
  public interface Listener {
188
    /**
189
     * Handles updates on resolved addresses and attributes.
190
     *
191
     * <p>Implementations will not modify the given {@code servers}.
192
     *
193
     * @param servers the resolved server addresses. An empty list will trigger {@link #onError}
194
     * @param attributes extra information from naming system.
195
     * @since 1.3.0
196
     */
197
    void onAddresses(
198
        List<EquivalentAddressGroup> servers, @ResolutionResultAttr Attributes attributes);
199

200
    /**
201
     * Handles an error from the resolver. The listener is responsible for eventually invoking
202
     * {@link #refresh()} to re-attempt resolution.
203
     *
204
     * @param error a non-OK status
205
     * @since 1.0.0
206
     */
207
    void onError(Status error);
208
  }
209

210
  /**
211
   * Receives address updates.
212
   *
213
   * <p>All methods are expected to return quickly.
214
   *
215
   * <p>This is a replacement API of {@code Listener}. However, we think this new API may change
216
   * again, so we aren't yet encouraging mass-migration to it. It is fine to use and works.
217
   *
218
   * @since 1.21.0
219
   */
220
  @ExperimentalApi("https://github.com/grpc/grpc-java/issues/1770")
221
  public abstract static class Listener2 implements Listener {
1✔
222
    /**
223
     * Handles updates on resolved addresses and attributes.
224
     *
225
     * @deprecated This will be removed in 1.22.0
226
     */
227
    @Override
228
    @Deprecated
229
    @InlineMe(
230
        replacement = "this.onResult(ResolutionResult.newBuilder().setAddressesOrError("
231
            + "StatusOr.fromValue(servers)).setAttributes(attributes).build())",
232
        imports = {"io.grpc.NameResolver.ResolutionResult", "io.grpc.StatusOr"})
233
    public final void onAddresses(
234
        List<EquivalentAddressGroup> servers, @ResolutionResultAttr Attributes attributes) {
235
      // TODO(jihuncho) need to promote Listener2 if we want to use ConfigOrError
236
      // Calling onResult and not onResult2 because onResult2 can only be called from a
237
      // synchronization context.
238
      onResult(
1✔
239
          ResolutionResult.newBuilder().setAddressesOrError(
1✔
240
              StatusOr.fromValue(servers)).setAttributes(attributes).build());
1✔
241
    }
1✔
242

243
    /**
244
     * Handles updates on resolved addresses and attributes.  If
245
     * {@link ResolutionResult#getAddressesOrError()} is empty, {@link #onError(Status)} will be
246
     * called.
247
     *
248
     * <p>Newer NameResolver implementations should prefer calling onResult2. This method exists to
249
     * facilitate older {@link Listener} implementations to migrate to {@link Listener2}.
250
     *
251
     * @param resolutionResult the resolved server addresses, attributes, and Service Config.
252
     * @since 1.21.0
253
     */
254
    public abstract void onResult(ResolutionResult resolutionResult);
255

256
    /**
257
     * Handles a name resolving error from the resolver. The listener is responsible for eventually
258
     * invoking {@link NameResolver#refresh()} to re-attempt resolution.
259
     *
260
     * <p>New NameResolver implementations should prefer calling onResult2 which will have the
261
     * address resolution error in {@link ResolutionResult}'s addressesOrError. This method exists
262
     * to facilitate older implementations using {@link Listener} to migrate to {@link Listener2}.
263
     *
264
     * @param error a non-OK status
265
     * @since 1.21.0
266
     */
267
    @Override
268
    public abstract void onError(Status error);
269

270
    /**
271
     * Handles updates on resolved addresses and attributes. Must be called from the same
272
     * {@link SynchronizationContext} available in {@link NameResolver.Args} that is passed
273
     * from the channel.
274
     *
275
     * @param resolutionResult the resolved server addresses or error in address resolution,
276
     *     attributes, and Service Config or error
277
     * @return status indicating whether the resolutionResult was accepted by the listener,
278
     *     typically the result from a load balancer.
279
     * @since 1.66
280
     */
281
    public Status onResult2(ResolutionResult resolutionResult) {
282
      onResult(resolutionResult);
×
283
      return Status.OK;
×
284
    }
285
  }
286

287
  /**
288
   * Annotation for name resolution result attributes. It follows the annotation semantics defined
289
   * by {@link Attributes}.
290
   */
291
  @ExperimentalApi("https://github.com/grpc/grpc-java/issues/4972")
292
  @Retention(RetentionPolicy.SOURCE)
293
  @Documented
294
  public @interface ResolutionResultAttr {}
295

296
  @ExperimentalApi("https://github.com/grpc/grpc-java/issues/11989")
297
  @ResolutionResultAttr
298
  public static final Attributes.Key<String> ATTR_BACKEND_SERVICE =
1✔
299
      Attributes.Key.create("io.grpc.NameResolver.ATTR_BACKEND_SERVICE");
1✔
300

301
  /**
302
   * Information that a {@link Factory} uses to create a {@link NameResolver}.
303
   *
304
   * <p>Args applicable to all {@link NameResolver}s are defined here using ordinary setters and
305
   * getters. This container can also hold externally-defined "custom" args that aren't so widely
306
   * useful or that would be inappropriate dependencies for this low level API. See {@link
307
   * Args#getArg} for more.
308
   *
309
   * <p>Note this class overrides neither {@code equals()} nor {@code hashCode()}.
310
   *
311
   * @since 1.21.0
312
   */
313
  @ExperimentalApi("https://github.com/grpc/grpc-java/issues/1770")
314
  public static final class Args {
315
    private final int defaultPort;
316
    private final ProxyDetector proxyDetector;
317
    private final SynchronizationContext syncContext;
318
    private final ServiceConfigParser serviceConfigParser;
319
    @Nullable private final ScheduledExecutorService scheduledExecutorService;
320
    @Nullable private final ChannelLogger channelLogger;
321
    @Nullable private final Executor executor;
322
    @Nullable private final String overrideAuthority;
323
    @Nullable private final MetricRecorder metricRecorder;
324
    @Nullable private final NameResolverRegistry nameResolverRegistry;
325
    @Nullable private final IdentityHashMap<Key<?>, Object> customArgs;
326

327
    private Args(Builder builder) {
1✔
328
      this.defaultPort = checkNotNull(builder.defaultPort, "defaultPort not set");
1✔
329
      this.proxyDetector = checkNotNull(builder.proxyDetector, "proxyDetector not set");
1✔
330
      this.syncContext = checkNotNull(builder.syncContext, "syncContext not set");
1✔
331
      this.serviceConfigParser =
1✔
332
          checkNotNull(builder.serviceConfigParser, "serviceConfigParser not set");
1✔
333
      this.scheduledExecutorService = builder.scheduledExecutorService;
1✔
334
      this.channelLogger = builder.channelLogger;
1✔
335
      this.executor = builder.executor;
1✔
336
      this.overrideAuthority = builder.overrideAuthority;
1✔
337
      this.metricRecorder = builder.metricRecorder;
1✔
338
      this.nameResolverRegistry = builder.nameResolverRegistry;
1✔
339
      this.customArgs = cloneCustomArgs(builder.customArgs);
1✔
340
    }
1✔
341

342
    /**
343
     * The port number used in case the target or the underlying naming system doesn't provide a
344
     * port number.
345
     *
346
     * @since 1.21.0
347
     */
348
    // <p>TODO: Only meaningful for InetSocketAddress producers. Make this a custom arg?
349
    public int getDefaultPort() {
350
      return defaultPort;
1✔
351
    }
352

353
    /**
354
     * If the NameResolver wants to support proxy, it should inquire this {@link ProxyDetector}.
355
     * See documentation on {@link ProxyDetector} about how proxies work in gRPC.
356
     *
357
     * @since 1.21.0
358
     */
359
    public ProxyDetector getProxyDetector() {
360
      return proxyDetector;
1✔
361
    }
362

363
    /**
364
     * Returns the {@link SynchronizationContext} where {@link #start(Listener2)}, {@link #shutdown}
365
     * and {@link #refresh} are run from.
366
     *
367
     * @since 1.21.0
368
     */
369
    public SynchronizationContext getSynchronizationContext() {
370
      return syncContext;
1✔
371
    }
372

373
    /**
374
     * Returns a {@link ScheduledExecutorService} for scheduling delayed tasks.
375
     *
376
     * <p>This service is a shared resource and is only meant for quick tasks. DO NOT block or run
377
     * time-consuming tasks.
378
     *
379
     * <p>The returned service doesn't support {@link ScheduledExecutorService#shutdown shutdown()}
380
     *  and {@link ScheduledExecutorService#shutdownNow shutdownNow()}. They will throw if called.
381
     *
382
     * @since 1.26.0
383
     */
384
    @ExperimentalApi("https://github.com/grpc/grpc-java/issues/6454")
385
    public ScheduledExecutorService getScheduledExecutorService() {
386
      if (scheduledExecutorService == null) {
1✔
387
        throw new IllegalStateException("ScheduledExecutorService not set in Builder");
×
388
      }
389
      return scheduledExecutorService;
1✔
390
    }
391

392
    /**
393
     * Returns the {@link ServiceConfigParser}.
394
     *
395
     * @since 1.21.0
396
     */
397
    public ServiceConfigParser getServiceConfigParser() {
398
      return serviceConfigParser;
1✔
399
    }
400

401
    /**
402
     * Returns the value of a custom arg named 'key', or {@code null} if it's not set.
403
     *
404
     * <p>While ordinary {@link Args} should be universally useful and meaningful, custom arguments
405
     * can apply just to resolvers of a certain URI scheme, just to resolvers producing a particular
406
     * type of {@link java.net.SocketAddress}, or even an individual {@link NameResolver} subclass.
407
     * Custom args are identified by an instance of {@link Args.Key} which should be a constant
408
     * defined in a java package and class appropriate for the argument's scope.
409
     *
410
     * <p>{@link Args} are normally reserved for information in *support* of name resolution, not
411
     * the name to be resolved itself. However, there are rare cases where all or part of the target
412
     * name can't be represented by any standard URI scheme or can't be encoded as a String at all.
413
     * Custom args, in contrast, can hold arbitrary Java types, making them a useful work around in
414
     * these cases.
415
     *
416
     * <p>Custom args can also be used simply to avoid adding inappropriate deps to the low level
417
     * io.grpc package.
418
     */
419
    @SuppressWarnings("unchecked") // Cast is safe because all put()s go through the setArg() API.
420
    @Nullable
421
    public <T> T getArg(Key<T> key) {
422
      return customArgs != null ? (T) customArgs.get(key) : null;
1✔
423
    }
424

425
    /**
426
     * Returns the {@link ChannelLogger} for the Channel served by this NameResolver.
427
     *
428
     * @since 1.26.0
429
     */
430
    @ExperimentalApi("https://github.com/grpc/grpc-java/issues/6438")
431
    public ChannelLogger getChannelLogger() {
432
      if (channelLogger == null) {
1✔
433
        throw new IllegalStateException("ChannelLogger is not set in Builder");
×
434
      }
435
      return channelLogger;
1✔
436
    }
437

438
    /**
439
     * Returns the Executor on which this resolver should execute long-running or I/O bound work.
440
     * Null if no Executor was set.
441
     *
442
     * @since 1.25.0
443
     */
444
    @Nullable
445
    public Executor getOffloadExecutor() {
446
      return executor;
1✔
447
    }
448

449
    /**
450
     * Returns the overrideAuthority from channel {@link ManagedChannelBuilder#overrideAuthority}.
451
     * Overrides the host name for L7 HTTP virtual host matching. Almost all name resolvers should
452
     * not use this.
453
     *
454
     * @since 1.49.0
455
     */
456
    @Nullable
457
    @ExperimentalApi("https://github.com/grpc/grpc-java/issues/9406")
458
    public String getOverrideAuthority() {
459
      return overrideAuthority;
1✔
460
    }
461

462
    /**
463
     * Returns the {@link MetricRecorder} that the channel uses to record metrics.
464
     */
465
    @Nullable
466
    public MetricRecorder getMetricRecorder() {
467
      return metricRecorder;
1✔
468
    }
469

470
    /**
471
     * Returns the {@link NameResolverRegistry} that the Channel uses to look for {@link
472
     * NameResolver}s.
473
     *
474
     * @since 1.74.0
475
     */
476
    public NameResolverRegistry getNameResolverRegistry() {
477
      if (nameResolverRegistry == null) {
1✔
478
        throw new IllegalStateException("NameResolverRegistry is not set in Builder");
×
479
      }
480
      return nameResolverRegistry;
1✔
481
    }
482

483
    @Override
484
    public String toString() {
485
      return MoreObjects.toStringHelper(this)
×
486
          .add("defaultPort", defaultPort)
×
487
          .add("proxyDetector", proxyDetector)
×
488
          .add("syncContext", syncContext)
×
489
          .add("serviceConfigParser", serviceConfigParser)
×
490
          .add("customArgs", customArgs)
×
491
          .add("scheduledExecutorService", scheduledExecutorService)
×
492
          .add("channelLogger", channelLogger)
×
493
          .add("executor", executor)
×
494
          .add("overrideAuthority", overrideAuthority)
×
495
          .add("metricRecorder", metricRecorder)
×
496
          .add("nameResolverRegistry", nameResolverRegistry)
×
497
          .toString();
×
498
    }
499

500
    /**
501
     * Returns a builder with the same initial values as this object.
502
     *
503
     * @since 1.21.0
504
     */
505
    public Builder toBuilder() {
506
      Builder builder = new Builder();
1✔
507
      builder.setDefaultPort(defaultPort);
1✔
508
      builder.setProxyDetector(proxyDetector);
1✔
509
      builder.setSynchronizationContext(syncContext);
1✔
510
      builder.setServiceConfigParser(serviceConfigParser);
1✔
511
      builder.setScheduledExecutorService(scheduledExecutorService);
1✔
512
      builder.setChannelLogger(channelLogger);
1✔
513
      builder.setOffloadExecutor(executor);
1✔
514
      builder.setOverrideAuthority(overrideAuthority);
1✔
515
      builder.setMetricRecorder(metricRecorder);
1✔
516
      builder.setNameResolverRegistry(nameResolverRegistry);
1✔
517
      builder.customArgs = cloneCustomArgs(customArgs);
1✔
518
      return builder;
1✔
519
    }
520

521
    /**
522
     * Creates a new builder.
523
     *
524
     * @since 1.21.0
525
     */
526
    public static Builder newBuilder() {
527
      return new Builder();
1✔
528
    }
529

530
    /**
531
     * Builder for {@link Args}.
532
     *
533
     * @since 1.21.0
534
     */
535
    public static final class Builder {
536
      private Integer defaultPort;
537
      private ProxyDetector proxyDetector;
538
      private SynchronizationContext syncContext;
539
      private ServiceConfigParser serviceConfigParser;
540
      private ScheduledExecutorService scheduledExecutorService;
541
      private ChannelLogger channelLogger;
542
      private Executor executor;
543
      private String overrideAuthority;
544
      private MetricRecorder metricRecorder;
545
      private NameResolverRegistry nameResolverRegistry;
546
      private IdentityHashMap<Key<?>, Object> customArgs;
547

548
      Builder() {
1✔
549
      }
1✔
550

551
      /**
552
       * See {@link Args#getDefaultPort}.  This is a required field.
553
       *
554
       * @since 1.21.0
555
       */
556
      public Builder setDefaultPort(int defaultPort) {
557
        this.defaultPort = defaultPort;
1✔
558
        return this;
1✔
559
      }
560

561
      /**
562
       * See {@link Args#getProxyDetector}.  This is required field.
563
       *
564
       * @since 1.21.0
565
       */
566
      public Builder setProxyDetector(ProxyDetector proxyDetector) {
567
        this.proxyDetector = checkNotNull(proxyDetector);
1✔
568
        return this;
1✔
569
      }
570

571
      /**
572
       * See {@link Args#getSynchronizationContext}.  This is a required field.
573
       *
574
       * @since 1.21.0
575
       */
576
      public Builder setSynchronizationContext(SynchronizationContext syncContext) {
577
        this.syncContext = checkNotNull(syncContext);
1✔
578
        return this;
1✔
579
      }
580

581
      /**
582
       * See {@link Args#getScheduledExecutorService}.
583
       */
584
      @ExperimentalApi("https://github.com/grpc/grpc-java/issues/6454")
585
      public Builder setScheduledExecutorService(
586
          ScheduledExecutorService scheduledExecutorService) {
587
        this.scheduledExecutorService = checkNotNull(scheduledExecutorService);
1✔
588
        return this;
1✔
589
      }
590

591
      /**
592
       * See {@link Args#getServiceConfigParser}.  This is a required field.
593
       *
594
       * @since 1.21.0
595
       */
596
      public Builder setServiceConfigParser(ServiceConfigParser parser) {
597
        this.serviceConfigParser = checkNotNull(parser);
1✔
598
        return this;
1✔
599
      }
600

601
      /**
602
       * See {@link Args#getChannelLogger}.
603
       *
604
       * @since 1.26.0
605
       */
606
      @ExperimentalApi("https://github.com/grpc/grpc-java/issues/6438")
607
      public Builder setChannelLogger(ChannelLogger channelLogger) {
608
        this.channelLogger = checkNotNull(channelLogger);
1✔
609
        return this;
1✔
610
      }
611

612
      /**
613
       * See {@link Args#getOffloadExecutor}. This is an optional field.
614
       *
615
       * @since 1.25.0
616
       */
617
      public Builder setOffloadExecutor(Executor executor) {
618
        this.executor = executor;
1✔
619
        return this;
1✔
620
      }
621

622
      /**
623
       * See {@link Args#getOverrideAuthority()}. This is an optional field.
624
       *
625
       * @since 1.49.0
626
       */
627
      @ExperimentalApi("https://github.com/grpc/grpc-java/issues/9406")
628
      public Builder setOverrideAuthority(String authority) {
629
        this.overrideAuthority = authority;
1✔
630
        return this;
1✔
631
      }
632

633
      /** See {@link Args#getArg(Key)}. */
634
      public <T> Builder setArg(Key<T> key, T value) {
635
        checkNotNull(key, "key");
1✔
636
        checkNotNull(value, "value");
1✔
637
        if (customArgs == null) {
1✔
638
          customArgs = new IdentityHashMap<>();
1✔
639
        }
640
        customArgs.put(key, value);
1✔
641
        return this;
1✔
642
      }
643

644
      /**
645
       * See {@link Args#getMetricRecorder()}. This is an optional field.
646
       */
647
      public Builder setMetricRecorder(MetricRecorder metricRecorder) {
648
        this.metricRecorder = metricRecorder;
1✔
649
        return this;
1✔
650
      }
651

652
      /**
653
       * See {@link Args#getNameResolverRegistry}.  This is an optional field.
654
       *
655
       * @since 1.74.0
656
       */
657
      public Builder setNameResolverRegistry(NameResolverRegistry registry) {
658
        this.nameResolverRegistry = registry;
1✔
659
        return this;
1✔
660
      }
661

662
      /**
663
       * Builds an {@link Args}.
664
       *
665
       * @since 1.21.0
666
       */
667
      public Args build() {
668
        return new Args(this);
1✔
669
      }
670
    }
671

672
    /**
673
     * Identifies an externally-defined custom argument that can be stored in {@link Args}.
674
     *
675
     * <p>Uses reference equality so keys should be defined as global constants.
676
     *
677
     * @param <T> type of values that can be stored under this key
678
     */
679
    @Immutable
680
    @SuppressWarnings("UnusedTypeParameter")
681
    public static final class Key<T> {
682
      private final String debugString;
683

684
      private Key(String debugString) {
1✔
685
        this.debugString = debugString;
1✔
686
      }
1✔
687

688
      @Override
689
      public String toString() {
690
        return debugString;
×
691
      }
692

693
      /**
694
       * Creates a new instance of {@link Key}.
695
       *
696
       * @param debugString a string used to describe the key, used for debugging.
697
       * @param <T> Key type
698
       * @return a new instance of Key
699
       */
700
      public static <T> Key<T> create(String debugString) {
701
        return new Key<>(debugString);
1✔
702
      }
703
    }
704
  }
705

706
  /**
707
   * Parses and validates service configuration.
708
   *
709
   * @since 1.21.0
710
   */
711
  @ExperimentalApi("https://github.com/grpc/grpc-java/issues/1770")
712
  public abstract static class ServiceConfigParser {
1✔
713
    /**
714
     * Parses and validates the service configuration chosen by the name resolver.  This will
715
     * return a {@link ConfigOrError} which contains either the successfully parsed config, or the
716
     * {@link Status} representing the failure to parse.  Implementations are expected to not throw
717
     * exceptions but return a Status representing the failure.  The value inside the
718
     * {@link ConfigOrError} should implement {@code equals()} and {@code hashCode()}.
719
     *
720
     * @param rawServiceConfig The {@link Map} representation of the service config
721
     * @return a tuple of the fully parsed and validated channel configuration, else the Status.
722
     * @since 1.21.0
723
     */
724
    public abstract ConfigOrError parseServiceConfig(Map<String, ?> rawServiceConfig);
725
  }
726

727
  /**
728
   * Represents the results from a Name Resolver.
729
   *
730
   * @since 1.21.0
731
   */
732
  @ExperimentalApi("https://github.com/grpc/grpc-java/issues/1770")
733
  public static final class ResolutionResult {
734
    private final StatusOr<List<EquivalentAddressGroup>> addressesOrError;
735
    @ResolutionResultAttr
736
    private final Attributes attributes;
737
    @Nullable
738
    private final ConfigOrError serviceConfig;
739

740
    ResolutionResult(
741
        StatusOr<List<EquivalentAddressGroup>> addressesOrError,
742
        @ResolutionResultAttr Attributes attributes,
743
        ConfigOrError serviceConfig) {
1✔
744
      this.addressesOrError = addressesOrError;
1✔
745
      this.attributes = checkNotNull(attributes, "attributes");
1✔
746
      this.serviceConfig = serviceConfig;
1✔
747
    }
1✔
748

749
    /**
750
     * Constructs a new builder of a name resolution result.
751
     *
752
     * @since 1.21.0
753
     */
754
    public static Builder newBuilder() {
755
      return new Builder();
1✔
756
    }
757

758
    /**
759
     * Converts these results back to a builder.
760
     *
761
     * @since 1.21.0
762
     */
763
    public Builder toBuilder() {
764
      return newBuilder()
×
765
          .setAddressesOrError(addressesOrError)
×
766
          .setAttributes(attributes)
×
767
          .setServiceConfig(serviceConfig);
×
768
    }
769

770
    /**
771
     * Gets the addresses resolved by name resolution.
772
     *
773
     * @since 1.21.0
774
     * @deprecated Will be superseded by getAddressesOrError
775
     */
776
    @Deprecated
777
    public List<EquivalentAddressGroup> getAddresses() {
778
      return addressesOrError.getValue();
×
779
    }
780

781
    /**
782
     * Gets the addresses resolved by name resolution or the error in doing so.
783
     *
784
     * @since 1.65.0
785
     */
786
    public StatusOr<List<EquivalentAddressGroup>> getAddressesOrError() {
787
      return addressesOrError;
1✔
788
    }
789

790
    /**
791
     * Gets the attributes associated with the addresses resolved by name resolution.  If there are
792
     * no attributes, {@link Attributes#EMPTY} will be returned.
793
     *
794
     * @since 1.21.0
795
     */
796
    @ResolutionResultAttr
797
    public Attributes getAttributes() {
798
      return attributes;
1✔
799
    }
800

801
    /**
802
     * Gets the Service Config parsed by {@link Args#getServiceConfigParser}.
803
     *
804
     * @since 1.21.0
805
     */
806
    @Nullable
807
    public ConfigOrError getServiceConfig() {
808
      return serviceConfig;
1✔
809
    }
810

811
    @Override
812
    public String toString() {
813
      ToStringHelper stringHelper = MoreObjects.toStringHelper(this);
1✔
814
      stringHelper.add("addressesOrError", addressesOrError.toString());
1✔
815
      stringHelper.add("attributes", attributes);
1✔
816
      stringHelper.add("serviceConfigOrError", serviceConfig);
1✔
817
      return stringHelper.toString();
1✔
818
    }
819

820
    /**
821
     * Useful for testing.  May be slow to calculate.
822
     */
823
    @Override
824
    public boolean equals(Object obj) {
825
      if (!(obj instanceof ResolutionResult)) {
×
826
        return false;
×
827
      }
828
      ResolutionResult that = (ResolutionResult) obj;
×
829
      return Objects.equal(this.addressesOrError, that.addressesOrError)
×
830
          && Objects.equal(this.attributes, that.attributes)
×
831
          && Objects.equal(this.serviceConfig, that.serviceConfig);
×
832
    }
833

834
    /**
835
     * Useful for testing.  May be slow to calculate.
836
     */
837
    @Override
838
    public int hashCode() {
839
      return Objects.hashCode(addressesOrError, attributes, serviceConfig);
1✔
840
    }
841

842
    /**
843
     * A builder for {@link ResolutionResult}.
844
     *
845
     * @since 1.21.0
846
     */
847
    @ExperimentalApi("https://github.com/grpc/grpc-java/issues/1770")
848
    public static final class Builder {
849
      private StatusOr<List<EquivalentAddressGroup>> addresses =
1✔
850
          StatusOr.fromValue(Collections.emptyList());
1✔
851
      private Attributes attributes = Attributes.EMPTY;
1✔
852
      @Nullable
853
      private ConfigOrError serviceConfig;
854
      //  Make sure to update #toBuilder above!
855

856
      Builder() {}
1✔
857

858
      /**
859
       * Sets the addresses resolved by name resolution.  This field is required.
860
       *
861
       * @since 1.21.0
862
       * @deprecated Will be superseded by setAddressesOrError
863
       */
864
      @Deprecated
865
      public Builder setAddresses(List<EquivalentAddressGroup> addresses) {
866
        setAddressesOrError(StatusOr.fromValue(addresses));
1✔
867
        return this;
1✔
868
      }
869

870
      /**
871
       * Sets the addresses resolved by name resolution or the error in doing so. This field is
872
       * required.
873
       * @param addresses Resolved addresses or an error in resolving addresses
874
       */
875
      public Builder setAddressesOrError(StatusOr<List<EquivalentAddressGroup>> addresses) {
876
        this.addresses = checkNotNull(addresses, "StatusOr addresses cannot be null.");
1✔
877
        return this;
1✔
878
      }
879

880
      /**
881
       * Sets the attributes for the addresses resolved by name resolution.  If unset,
882
       * {@link Attributes#EMPTY} will be used as a default.
883
       *
884
       * @since 1.21.0
885
       */
886
      public Builder setAttributes(Attributes attributes) {
887
        this.attributes = attributes;
1✔
888
        return this;
1✔
889
      }
890

891
      /**
892
       * Sets the Service Config parsed by {@link Args#getServiceConfigParser}.
893
       * This field is optional.
894
       *
895
       * @since 1.21.0
896
       */
897
      public Builder setServiceConfig(@Nullable ConfigOrError serviceConfig) {
898
        this.serviceConfig = serviceConfig;
1✔
899
        return this;
1✔
900
      }
901

902
      /**
903
       * Constructs a new {@link ResolutionResult} from this builder.
904
       *
905
       * @since 1.21.0
906
       */
907
      public ResolutionResult build() {
908
        return new ResolutionResult(addresses, attributes, serviceConfig);
1✔
909
      }
910
    }
911
  }
912

913
  /**
914
   * Represents either a successfully parsed service config, containing all necessary parts to be
915
   * later applied by the channel, or a Status containing the error encountered while parsing.
916
   *
917
   * @since 1.20.0
918
   */
919
  @ExperimentalApi("https://github.com/grpc/grpc-java/issues/1770")
1✔
920
  public static final class ConfigOrError {
921

922
    /**
923
     * Returns a {@link ConfigOrError} for the successfully parsed config.
924
     */
925
    public static ConfigOrError fromConfig(Object config) {
926
      return new ConfigOrError(config);
1✔
927
    }
928

929
    /**
930
     * Returns a {@link ConfigOrError} for the failure to parse the config.
931
     *
932
     * @param status a non-OK status
933
     */
934
    public static ConfigOrError fromError(Status status) {
935
      return new ConfigOrError(status);
1✔
936
    }
937

938
    private final Status status;
939
    private final Object config;
940

941
    private ConfigOrError(Object config) {
1✔
942
      this.config = checkNotNull(config, "config");
1✔
943
      this.status = null;
1✔
944
    }
1✔
945

946
    private ConfigOrError(Status status) {
1✔
947
      this.config = null;
1✔
948
      this.status = checkNotNull(status, "status");
1✔
949
      checkArgument(!status.isOk(), "cannot use OK status: %s", status);
1✔
950
    }
1✔
951

952
    /**
953
     * Returns config if exists, otherwise null.
954
     */
955
    @Nullable
956
    public Object getConfig() {
957
      return config;
1✔
958
    }
959

960
    /**
961
     * Returns error status if exists, otherwise null.
962
     */
963
    @Nullable
964
    public Status getError() {
965
      return status;
1✔
966
    }
967

968
    @Override
969
    public boolean equals(Object o) {
970
      if (this == o) {
1✔
971
        return true;
×
972
      }
973
      if (o == null || getClass() != o.getClass()) {
1✔
974
        return false;
×
975
      }
976
      ConfigOrError that = (ConfigOrError) o;
1✔
977
      return Objects.equal(status, that.status) && Objects.equal(config, that.config);
1✔
978
    }
979

980
    @Override
981
    public int hashCode() {
982
      return Objects.hashCode(status, config);
1✔
983
    }
984

985
    @Override
986
    public String toString() {
987
      if (config != null) {
1✔
988
        return MoreObjects.toStringHelper(this)
1✔
989
            .add("config", config)
1✔
990
            .toString();
1✔
991
      } else {
992
        assert status != null;
×
993
        return MoreObjects.toStringHelper(this)
×
994
            .add("error", status)
×
995
            .toString();
×
996
      }
997
    }
998
  }
999

1000
  @Nullable
1001
  private static IdentityHashMap<Args.Key<?>, Object> cloneCustomArgs(
1002
          @Nullable IdentityHashMap<Args.Key<?>, Object> customArgs) {
1003
    return customArgs != null ? new IdentityHashMap<>(customArgs) : null;
1✔
1004
  }
1005
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc