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

grpc / grpc-java / #20142

09 Jan 2026 01:58PM UTC coverage: 88.656% (-0.009%) from 88.665%
#20142

push

github

web-flow
api: Add RFC 3986 support to DnsNameResolverProvider (#12602)

Accept both absolute (e.g. `dns:///hostname`) and rootless (e.g.
`dns:hostname`) paths as specified by
https://github.com/grpc/grpc/blob/master/doc/naming.md and matching the
behavior of grpc core and grpc-go.

35331 of 39852 relevant lines covered (88.66%)

0.89 hits per line

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

78.95
/../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
     * <p>This method will eventually be deprecated and removed as part of a migration from {@code
162
     * java.net.URI} to {@code io.grpc.Uri}. Implementations will override {@link
163
     * #newNameResolver(Uri, Args)} instead.
164
     *
165
     * @param targetUri the target URI to be resolved, whose scheme must not be {@code null}
166
     * @param args other information that may be useful
167
     *
168
     * @since 1.21.0
169
     */
170
    public abstract NameResolver newNameResolver(URI targetUri, final Args args);
171

172
    /**
173
     * Creates a {@link NameResolver} for the given target URI.
174
     *
175
     * <p>Implementations return {@code null} if 'targetUri' cannot be resolved by this factory. The
176
     * decision should be solely based on the target's scheme.
177
     *
178
     * <p>All {@link NameResolver.Factory} implementations should override this method, as it will
179
     * eventually replace {@link #newNameResolver(URI, Args)}. For backwards compatibility, this
180
     * default implementation delegates to {@link #newNameResolver(URI, Args)} if 'targetUri' can be
181
     * converted to a java.net.URI.
182
     *
183
     * <p>NB: Conversion is not always possible, for example {@code scheme:#frag} is a valid {@link
184
     * Uri} but not a valid {@link URI} because its path is empty. The default implementation throws
185
     * IllegalArgumentException in these cases.
186
     *
187
     * @param targetUri the target URI to be resolved
188
     * @param args other information that may be useful
189
     * @throws IllegalArgumentException if targetUri does not have the expected form
190
     * @since 1.79
191
     */
192
    public NameResolver newNameResolver(Uri targetUri, final Args args) {
193
      // Not every io.grpc.Uri can be converted but in the ordinary ManagedChannel creation flow,
194
      // any IllegalArgumentException thrown here would happened anyway, just earlier. That's
195
      // because parse/toString is transparent so java.net.URI#create here sees the original target
196
      // string just like it did before the io.grpc.Uri migration.
197
      //
198
      // Throwing IAE shouldn't surprise non-framework callers either. After all, many existing
199
      // Factory impls are picky about targetUri and throw IAE when it doesn't look how they expect.
200
      return newNameResolver(URI.create(targetUri.toString()), args);
×
201
    }
202

203
    /**
204
     * Returns the default scheme, which will be used to construct a URI when {@link
205
     * ManagedChannelBuilder#forTarget(String)} is given an authority string instead of a compliant
206
     * URI.
207
     *
208
     * @since 1.0.0
209
     */
210
    public abstract String getDefaultScheme();
211
  }
212

213
  /**
214
   * Receives address updates.
215
   *
216
   * <p>All methods are expected to return quickly.
217
   *
218
   * @since 1.0.0
219
   */
220
  @ExperimentalApi("https://github.com/grpc/grpc-java/issues/1770")
221
  @ThreadSafe
222
  public interface Listener {
223
    /**
224
     * Handles updates on resolved addresses and attributes.
225
     *
226
     * <p>Implementations will not modify the given {@code servers}.
227
     *
228
     * @param servers the resolved server addresses. An empty list will trigger {@link #onError}
229
     * @param attributes extra information from naming system.
230
     * @since 1.3.0
231
     */
232
    void onAddresses(
233
        List<EquivalentAddressGroup> servers, @ResolutionResultAttr Attributes attributes);
234

235
    /**
236
     * Handles an error from the resolver. The listener is responsible for eventually invoking
237
     * {@link #refresh()} to re-attempt resolution.
238
     *
239
     * @param error a non-OK status
240
     * @since 1.0.0
241
     */
242
    void onError(Status error);
243
  }
244

245
  /**
246
   * Receives address updates.
247
   *
248
   * <p>All methods are expected to return quickly.
249
   *
250
   * <p>This is a replacement API of {@code Listener}. However, we think this new API may change
251
   * again, so we aren't yet encouraging mass-migration to it. It is fine to use and works.
252
   *
253
   * @since 1.21.0
254
   */
255
  @ExperimentalApi("https://github.com/grpc/grpc-java/issues/1770")
256
  public abstract static class Listener2 implements Listener {
1✔
257
    /**
258
     * Handles updates on resolved addresses and attributes.
259
     *
260
     * @deprecated This will be removed in 1.22.0
261
     */
262
    @Override
263
    @Deprecated
264
    @InlineMe(
265
        replacement = "this.onResult(ResolutionResult.newBuilder().setAddressesOrError("
266
            + "StatusOr.fromValue(servers)).setAttributes(attributes).build())",
267
        imports = {"io.grpc.NameResolver.ResolutionResult", "io.grpc.StatusOr"})
268
    public final void onAddresses(
269
        List<EquivalentAddressGroup> servers, @ResolutionResultAttr Attributes attributes) {
270
      // TODO(jihuncho) need to promote Listener2 if we want to use ConfigOrError
271
      // Calling onResult and not onResult2 because onResult2 can only be called from a
272
      // synchronization context.
273
      onResult(
1✔
274
          ResolutionResult.newBuilder().setAddressesOrError(
1✔
275
              StatusOr.fromValue(servers)).setAttributes(attributes).build());
1✔
276
    }
1✔
277

278
    /**
279
     * Handles updates on resolved addresses and attributes.  If
280
     * {@link ResolutionResult#getAddressesOrError()} is empty, {@link #onError(Status)} will be
281
     * called.
282
     *
283
     * <p>Newer NameResolver implementations should prefer calling onResult2. This method exists to
284
     * facilitate older {@link Listener} implementations to migrate to {@link Listener2}.
285
     *
286
     * @param resolutionResult the resolved server addresses, attributes, and Service Config.
287
     * @since 1.21.0
288
     */
289
    public abstract void onResult(ResolutionResult resolutionResult);
290

291
    /**
292
     * Handles a name resolving error from the resolver. The listener is responsible for eventually
293
     * invoking {@link NameResolver#refresh()} to re-attempt resolution.
294
     *
295
     * <p>New NameResolver implementations should prefer calling onResult2 which will have the
296
     * address resolution error in {@link ResolutionResult}'s addressesOrError. This method exists
297
     * to facilitate older implementations using {@link Listener} to migrate to {@link Listener2}.
298
     *
299
     * @param error a non-OK status
300
     * @since 1.21.0
301
     */
302
    @Override
303
    public abstract void onError(Status error);
304

305
    /**
306
     * Handles updates on resolved addresses and attributes. Must be called from the same
307
     * {@link SynchronizationContext} available in {@link NameResolver.Args} that is passed
308
     * from the channel.
309
     *
310
     * @param resolutionResult the resolved server addresses or error in address resolution,
311
     *     attributes, and Service Config or error
312
     * @return status indicating whether the resolutionResult was accepted by the listener,
313
     *     typically the result from a load balancer.
314
     * @since 1.66
315
     */
316
    public Status onResult2(ResolutionResult resolutionResult) {
317
      onResult(resolutionResult);
×
318
      return Status.OK;
×
319
    }
320
  }
321

322
  /**
323
   * Annotation for name resolution result attributes. It follows the annotation semantics defined
324
   * by {@link Attributes}.
325
   */
326
  @ExperimentalApi("https://github.com/grpc/grpc-java/issues/4972")
327
  @Retention(RetentionPolicy.SOURCE)
328
  @Documented
329
  public @interface ResolutionResultAttr {}
330

331
  @ExperimentalApi("https://github.com/grpc/grpc-java/issues/11989")
332
  @ResolutionResultAttr
333
  public static final Attributes.Key<String> ATTR_BACKEND_SERVICE =
1✔
334
      Attributes.Key.create("io.grpc.NameResolver.ATTR_BACKEND_SERVICE");
1✔
335

336
  /**
337
   * Information that a {@link Factory} uses to create a {@link NameResolver}.
338
   *
339
   * <p>Args applicable to all {@link NameResolver}s are defined here using ordinary setters and
340
   * getters. This container can also hold externally-defined "custom" args that aren't so widely
341
   * useful or that would be inappropriate dependencies for this low level API. See {@link
342
   * Args#getArg} for more.
343
   *
344
   * <p>Note this class overrides neither {@code equals()} nor {@code hashCode()}.
345
   *
346
   * @since 1.21.0
347
   */
348
  @ExperimentalApi("https://github.com/grpc/grpc-java/issues/1770")
349
  public static final class Args {
350
    private final int defaultPort;
351
    private final ProxyDetector proxyDetector;
352
    private final SynchronizationContext syncContext;
353
    private final ServiceConfigParser serviceConfigParser;
354
    @Nullable private final ScheduledExecutorService scheduledExecutorService;
355
    @Nullable private final ChannelLogger channelLogger;
356
    @Nullable private final Executor executor;
357
    @Nullable private final String overrideAuthority;
358
    @Nullable private final MetricRecorder metricRecorder;
359
    @Nullable private final NameResolverRegistry nameResolverRegistry;
360
    @Nullable private final IdentityHashMap<Key<?>, Object> customArgs;
361

362
    private Args(Builder builder) {
1✔
363
      this.defaultPort = checkNotNull(builder.defaultPort, "defaultPort not set");
1✔
364
      this.proxyDetector = checkNotNull(builder.proxyDetector, "proxyDetector not set");
1✔
365
      this.syncContext = checkNotNull(builder.syncContext, "syncContext not set");
1✔
366
      this.serviceConfigParser =
1✔
367
          checkNotNull(builder.serviceConfigParser, "serviceConfigParser not set");
1✔
368
      this.scheduledExecutorService = builder.scheduledExecutorService;
1✔
369
      this.channelLogger = builder.channelLogger;
1✔
370
      this.executor = builder.executor;
1✔
371
      this.overrideAuthority = builder.overrideAuthority;
1✔
372
      this.metricRecorder = builder.metricRecorder;
1✔
373
      this.nameResolverRegistry = builder.nameResolverRegistry;
1✔
374
      this.customArgs = cloneCustomArgs(builder.customArgs);
1✔
375
    }
1✔
376

377
    /**
378
     * The port number used in case the target or the underlying naming system doesn't provide a
379
     * port number.
380
     *
381
     * @since 1.21.0
382
     */
383
    // <p>TODO: Only meaningful for InetSocketAddress producers. Make this a custom arg?
384
    public int getDefaultPort() {
385
      return defaultPort;
1✔
386
    }
387

388
    /**
389
     * If the NameResolver wants to support proxy, it should inquire this {@link ProxyDetector}.
390
     * See documentation on {@link ProxyDetector} about how proxies work in gRPC.
391
     *
392
     * @since 1.21.0
393
     */
394
    public ProxyDetector getProxyDetector() {
395
      return proxyDetector;
1✔
396
    }
397

398
    /**
399
     * Returns the {@link SynchronizationContext} where {@link #start(Listener2)}, {@link #shutdown}
400
     * and {@link #refresh} are run from.
401
     *
402
     * @since 1.21.0
403
     */
404
    public SynchronizationContext getSynchronizationContext() {
405
      return syncContext;
1✔
406
    }
407

408
    /**
409
     * Returns a {@link ScheduledExecutorService} for scheduling delayed tasks.
410
     *
411
     * <p>This service is a shared resource and is only meant for quick tasks. DO NOT block or run
412
     * time-consuming tasks.
413
     *
414
     * <p>The returned service doesn't support {@link ScheduledExecutorService#shutdown shutdown()}
415
     *  and {@link ScheduledExecutorService#shutdownNow shutdownNow()}. They will throw if called.
416
     *
417
     * @since 1.26.0
418
     */
419
    @ExperimentalApi("https://github.com/grpc/grpc-java/issues/6454")
420
    public ScheduledExecutorService getScheduledExecutorService() {
421
      if (scheduledExecutorService == null) {
1✔
422
        throw new IllegalStateException("ScheduledExecutorService not set in Builder");
×
423
      }
424
      return scheduledExecutorService;
1✔
425
    }
426

427
    /**
428
     * Returns the {@link ServiceConfigParser}.
429
     *
430
     * @since 1.21.0
431
     */
432
    public ServiceConfigParser getServiceConfigParser() {
433
      return serviceConfigParser;
1✔
434
    }
435

436
    /**
437
     * Returns the value of a custom arg named 'key', or {@code null} if it's not set.
438
     *
439
     * <p>While ordinary {@link Args} should be universally useful and meaningful, custom arguments
440
     * can apply just to resolvers of a certain URI scheme, just to resolvers producing a particular
441
     * type of {@link java.net.SocketAddress}, or even an individual {@link NameResolver} subclass.
442
     * Custom args are identified by an instance of {@link Args.Key} which should be a constant
443
     * defined in a java package and class appropriate for the argument's scope.
444
     *
445
     * <p>{@link Args} are normally reserved for information in *support* of name resolution, not
446
     * the name to be resolved itself. However, there are rare cases where all or part of the target
447
     * name can't be represented by any standard URI scheme or can't be encoded as a String at all.
448
     * Custom args, in contrast, can hold arbitrary Java types, making them a useful work around in
449
     * these cases.
450
     *
451
     * <p>Custom args can also be used simply to avoid adding inappropriate deps to the low level
452
     * io.grpc package.
453
     */
454
    @SuppressWarnings("unchecked") // Cast is safe because all put()s go through the setArg() API.
455
    @Nullable
456
    public <T> T getArg(Key<T> key) {
457
      return customArgs != null ? (T) customArgs.get(key) : null;
1✔
458
    }
459

460
    /**
461
     * Returns the {@link ChannelLogger} for the Channel served by this NameResolver.
462
     *
463
     * @since 1.26.0
464
     */
465
    @ExperimentalApi("https://github.com/grpc/grpc-java/issues/6438")
466
    public ChannelLogger getChannelLogger() {
467
      if (channelLogger == null) {
1✔
468
        throw new IllegalStateException("ChannelLogger is not set in Builder");
×
469
      }
470
      return channelLogger;
1✔
471
    }
472

473
    /**
474
     * Returns the Executor on which this resolver should execute long-running or I/O bound work.
475
     * Null if no Executor was set.
476
     *
477
     * @since 1.25.0
478
     */
479
    @Nullable
480
    public Executor getOffloadExecutor() {
481
      return executor;
1✔
482
    }
483

484
    /**
485
     * Returns the overrideAuthority from channel {@link ManagedChannelBuilder#overrideAuthority}.
486
     * Overrides the host name for L7 HTTP virtual host matching. Almost all name resolvers should
487
     * not use this.
488
     *
489
     * @since 1.49.0
490
     */
491
    @Nullable
492
    @ExperimentalApi("https://github.com/grpc/grpc-java/issues/9406")
493
    public String getOverrideAuthority() {
494
      return overrideAuthority;
1✔
495
    }
496

497
    /**
498
     * Returns the {@link MetricRecorder} that the channel uses to record metrics.
499
     */
500
    @Nullable
501
    public MetricRecorder getMetricRecorder() {
502
      return metricRecorder;
1✔
503
    }
504

505
    /**
506
     * Returns the {@link NameResolverRegistry} that the Channel uses to look for {@link
507
     * NameResolver}s.
508
     *
509
     * @since 1.74.0
510
     */
511
    public NameResolverRegistry getNameResolverRegistry() {
512
      if (nameResolverRegistry == null) {
1✔
513
        throw new IllegalStateException("NameResolverRegistry is not set in Builder");
×
514
      }
515
      return nameResolverRegistry;
1✔
516
    }
517

518
    @Override
519
    public String toString() {
520
      return MoreObjects.toStringHelper(this)
×
521
          .add("defaultPort", defaultPort)
×
522
          .add("proxyDetector", proxyDetector)
×
523
          .add("syncContext", syncContext)
×
524
          .add("serviceConfigParser", serviceConfigParser)
×
525
          .add("customArgs", customArgs)
×
526
          .add("scheduledExecutorService", scheduledExecutorService)
×
527
          .add("channelLogger", channelLogger)
×
528
          .add("executor", executor)
×
529
          .add("overrideAuthority", overrideAuthority)
×
530
          .add("metricRecorder", metricRecorder)
×
531
          .add("nameResolverRegistry", nameResolverRegistry)
×
532
          .toString();
×
533
    }
534

535
    /**
536
     * Returns a builder with the same initial values as this object.
537
     *
538
     * @since 1.21.0
539
     */
540
    public Builder toBuilder() {
541
      Builder builder = new Builder();
1✔
542
      builder.setDefaultPort(defaultPort);
1✔
543
      builder.setProxyDetector(proxyDetector);
1✔
544
      builder.setSynchronizationContext(syncContext);
1✔
545
      builder.setServiceConfigParser(serviceConfigParser);
1✔
546
      builder.setScheduledExecutorService(scheduledExecutorService);
1✔
547
      builder.setChannelLogger(channelLogger);
1✔
548
      builder.setOffloadExecutor(executor);
1✔
549
      builder.setOverrideAuthority(overrideAuthority);
1✔
550
      builder.setMetricRecorder(metricRecorder);
1✔
551
      builder.setNameResolverRegistry(nameResolverRegistry);
1✔
552
      builder.customArgs = cloneCustomArgs(customArgs);
1✔
553
      return builder;
1✔
554
    }
555

556
    /**
557
     * Creates a new builder.
558
     *
559
     * @since 1.21.0
560
     */
561
    public static Builder newBuilder() {
562
      return new Builder();
1✔
563
    }
564

565
    /**
566
     * Builder for {@link Args}.
567
     *
568
     * @since 1.21.0
569
     */
570
    public static final class Builder {
571
      private Integer defaultPort;
572
      private ProxyDetector proxyDetector;
573
      private SynchronizationContext syncContext;
574
      private ServiceConfigParser serviceConfigParser;
575
      private ScheduledExecutorService scheduledExecutorService;
576
      private ChannelLogger channelLogger;
577
      private Executor executor;
578
      private String overrideAuthority;
579
      private MetricRecorder metricRecorder;
580
      private NameResolverRegistry nameResolverRegistry;
581
      private IdentityHashMap<Key<?>, Object> customArgs;
582

583
      Builder() {
1✔
584
      }
1✔
585

586
      /**
587
       * See {@link Args#getDefaultPort}.  This is a required field.
588
       *
589
       * @since 1.21.0
590
       */
591
      public Builder setDefaultPort(int defaultPort) {
592
        this.defaultPort = defaultPort;
1✔
593
        return this;
1✔
594
      }
595

596
      /**
597
       * See {@link Args#getProxyDetector}.  This is required field.
598
       *
599
       * @since 1.21.0
600
       */
601
      public Builder setProxyDetector(ProxyDetector proxyDetector) {
602
        this.proxyDetector = checkNotNull(proxyDetector);
1✔
603
        return this;
1✔
604
      }
605

606
      /**
607
       * See {@link Args#getSynchronizationContext}.  This is a required field.
608
       *
609
       * @since 1.21.0
610
       */
611
      public Builder setSynchronizationContext(SynchronizationContext syncContext) {
612
        this.syncContext = checkNotNull(syncContext);
1✔
613
        return this;
1✔
614
      }
615

616
      /**
617
       * See {@link Args#getScheduledExecutorService}.
618
       */
619
      @ExperimentalApi("https://github.com/grpc/grpc-java/issues/6454")
620
      public Builder setScheduledExecutorService(
621
          ScheduledExecutorService scheduledExecutorService) {
622
        this.scheduledExecutorService = checkNotNull(scheduledExecutorService);
1✔
623
        return this;
1✔
624
      }
625

626
      /**
627
       * See {@link Args#getServiceConfigParser}.  This is a required field.
628
       *
629
       * @since 1.21.0
630
       */
631
      public Builder setServiceConfigParser(ServiceConfigParser parser) {
632
        this.serviceConfigParser = checkNotNull(parser);
1✔
633
        return this;
1✔
634
      }
635

636
      /**
637
       * See {@link Args#getChannelLogger}.
638
       *
639
       * @since 1.26.0
640
       */
641
      @ExperimentalApi("https://github.com/grpc/grpc-java/issues/6438")
642
      public Builder setChannelLogger(ChannelLogger channelLogger) {
643
        this.channelLogger = checkNotNull(channelLogger);
1✔
644
        return this;
1✔
645
      }
646

647
      /**
648
       * See {@link Args#getOffloadExecutor}. This is an optional field.
649
       *
650
       * @since 1.25.0
651
       */
652
      public Builder setOffloadExecutor(Executor executor) {
653
        this.executor = executor;
1✔
654
        return this;
1✔
655
      }
656

657
      /**
658
       * See {@link Args#getOverrideAuthority()}. This is an optional field.
659
       *
660
       * @since 1.49.0
661
       */
662
      @ExperimentalApi("https://github.com/grpc/grpc-java/issues/9406")
663
      public Builder setOverrideAuthority(String authority) {
664
        this.overrideAuthority = authority;
1✔
665
        return this;
1✔
666
      }
667

668
      /** See {@link Args#getArg(Key)}. */
669
      public <T> Builder setArg(Key<T> key, T value) {
670
        checkNotNull(key, "key");
1✔
671
        checkNotNull(value, "value");
1✔
672
        if (customArgs == null) {
1✔
673
          customArgs = new IdentityHashMap<>();
1✔
674
        }
675
        customArgs.put(key, value);
1✔
676
        return this;
1✔
677
      }
678

679
      /**
680
       * See {@link Args#getMetricRecorder()}. This is an optional field.
681
       */
682
      public Builder setMetricRecorder(MetricRecorder metricRecorder) {
683
        this.metricRecorder = metricRecorder;
1✔
684
        return this;
1✔
685
      }
686

687
      /**
688
       * See {@link Args#getNameResolverRegistry}.  This is an optional field.
689
       *
690
       * @since 1.74.0
691
       */
692
      public Builder setNameResolverRegistry(NameResolverRegistry registry) {
693
        this.nameResolverRegistry = registry;
1✔
694
        return this;
1✔
695
      }
696

697
      /**
698
       * Builds an {@link Args}.
699
       *
700
       * @since 1.21.0
701
       */
702
      public Args build() {
703
        return new Args(this);
1✔
704
      }
705
    }
706

707
    /**
708
     * Identifies an externally-defined custom argument that can be stored in {@link Args}.
709
     *
710
     * <p>Uses reference equality so keys should be defined as global constants.
711
     *
712
     * @param <T> type of values that can be stored under this key
713
     */
714
    @Immutable
715
    @SuppressWarnings("UnusedTypeParameter")
716
    public static final class Key<T> {
717
      private final String debugString;
718

719
      private Key(String debugString) {
1✔
720
        this.debugString = debugString;
1✔
721
      }
1✔
722

723
      @Override
724
      public String toString() {
725
        return debugString;
×
726
      }
727

728
      /**
729
       * Creates a new instance of {@link Key}.
730
       *
731
       * @param debugString a string used to describe the key, used for debugging.
732
       * @param <T> Key type
733
       * @return a new instance of Key
734
       */
735
      public static <T> Key<T> create(String debugString) {
736
        return new Key<>(debugString);
1✔
737
      }
738
    }
739
  }
740

741
  /**
742
   * Parses and validates service configuration.
743
   *
744
   * @since 1.21.0
745
   */
746
  @ExperimentalApi("https://github.com/grpc/grpc-java/issues/1770")
747
  public abstract static class ServiceConfigParser {
1✔
748
    /**
749
     * Parses and validates the service configuration chosen by the name resolver.  This will
750
     * return a {@link ConfigOrError} which contains either the successfully parsed config, or the
751
     * {@link Status} representing the failure to parse.  Implementations are expected to not throw
752
     * exceptions but return a Status representing the failure.  The value inside the
753
     * {@link ConfigOrError} should implement {@code equals()} and {@code hashCode()}.
754
     *
755
     * @param rawServiceConfig The {@link Map} representation of the service config
756
     * @return a tuple of the fully parsed and validated channel configuration, else the Status.
757
     * @since 1.21.0
758
     */
759
    public abstract ConfigOrError parseServiceConfig(Map<String, ?> rawServiceConfig);
760
  }
761

762
  /**
763
   * Represents the results from a Name Resolver.
764
   *
765
   * @since 1.21.0
766
   */
767
  @ExperimentalApi("https://github.com/grpc/grpc-java/issues/1770")
768
  public static final class ResolutionResult {
769
    private final StatusOr<List<EquivalentAddressGroup>> addressesOrError;
770
    @ResolutionResultAttr
771
    private final Attributes attributes;
772
    @Nullable
773
    private final ConfigOrError serviceConfig;
774

775
    ResolutionResult(
776
        StatusOr<List<EquivalentAddressGroup>> addressesOrError,
777
        @ResolutionResultAttr Attributes attributes,
778
        ConfigOrError serviceConfig) {
1✔
779
      this.addressesOrError = addressesOrError;
1✔
780
      this.attributes = checkNotNull(attributes, "attributes");
1✔
781
      this.serviceConfig = serviceConfig;
1✔
782
    }
1✔
783

784
    /**
785
     * Constructs a new builder of a name resolution result.
786
     *
787
     * @since 1.21.0
788
     */
789
    public static Builder newBuilder() {
790
      return new Builder();
1✔
791
    }
792

793
    /**
794
     * Converts these results back to a builder.
795
     *
796
     * @since 1.21.0
797
     */
798
    public Builder toBuilder() {
799
      return newBuilder()
×
800
          .setAddressesOrError(addressesOrError)
×
801
          .setAttributes(attributes)
×
802
          .setServiceConfig(serviceConfig);
×
803
    }
804

805
    /**
806
     * Gets the addresses resolved by name resolution.
807
     *
808
     * @since 1.21.0
809
     * @deprecated Will be superseded by getAddressesOrError
810
     */
811
    @Deprecated
812
    public List<EquivalentAddressGroup> getAddresses() {
813
      return addressesOrError.getValue();
×
814
    }
815

816
    /**
817
     * Gets the addresses resolved by name resolution or the error in doing so.
818
     *
819
     * @since 1.65.0
820
     */
821
    public StatusOr<List<EquivalentAddressGroup>> getAddressesOrError() {
822
      return addressesOrError;
1✔
823
    }
824

825
    /**
826
     * Gets the attributes associated with the addresses resolved by name resolution.  If there are
827
     * no attributes, {@link Attributes#EMPTY} will be returned.
828
     *
829
     * @since 1.21.0
830
     */
831
    @ResolutionResultAttr
832
    public Attributes getAttributes() {
833
      return attributes;
1✔
834
    }
835

836
    /**
837
     * Gets the Service Config parsed by {@link Args#getServiceConfigParser}.
838
     *
839
     * @since 1.21.0
840
     */
841
    @Nullable
842
    public ConfigOrError getServiceConfig() {
843
      return serviceConfig;
1✔
844
    }
845

846
    @Override
847
    public String toString() {
848
      ToStringHelper stringHelper = MoreObjects.toStringHelper(this);
1✔
849
      stringHelper.add("addressesOrError", addressesOrError.toString());
1✔
850
      stringHelper.add("attributes", attributes);
1✔
851
      stringHelper.add("serviceConfigOrError", serviceConfig);
1✔
852
      return stringHelper.toString();
1✔
853
    }
854

855
    /**
856
     * Useful for testing.  May be slow to calculate.
857
     */
858
    @Override
859
    public boolean equals(Object obj) {
860
      if (!(obj instanceof ResolutionResult)) {
×
861
        return false;
×
862
      }
863
      ResolutionResult that = (ResolutionResult) obj;
×
864
      return Objects.equal(this.addressesOrError, that.addressesOrError)
×
865
          && Objects.equal(this.attributes, that.attributes)
×
866
          && Objects.equal(this.serviceConfig, that.serviceConfig);
×
867
    }
868

869
    /**
870
     * Useful for testing.  May be slow to calculate.
871
     */
872
    @Override
873
    public int hashCode() {
874
      return Objects.hashCode(addressesOrError, attributes, serviceConfig);
1✔
875
    }
876

877
    /**
878
     * A builder for {@link ResolutionResult}.
879
     *
880
     * @since 1.21.0
881
     */
882
    @ExperimentalApi("https://github.com/grpc/grpc-java/issues/1770")
883
    public static final class Builder {
884
      private StatusOr<List<EquivalentAddressGroup>> addresses =
1✔
885
          StatusOr.fromValue(Collections.emptyList());
1✔
886
      private Attributes attributes = Attributes.EMPTY;
1✔
887
      @Nullable
888
      private ConfigOrError serviceConfig;
889
      //  Make sure to update #toBuilder above!
890

891
      Builder() {}
1✔
892

893
      /**
894
       * Sets the addresses resolved by name resolution.  This field is required.
895
       *
896
       * @since 1.21.0
897
       * @deprecated Will be superseded by setAddressesOrError
898
       */
899
      @Deprecated
900
      public Builder setAddresses(List<EquivalentAddressGroup> addresses) {
901
        setAddressesOrError(StatusOr.fromValue(addresses));
1✔
902
        return this;
1✔
903
      }
904

905
      /**
906
       * Sets the addresses resolved by name resolution or the error in doing so. This field is
907
       * required.
908
       * @param addresses Resolved addresses or an error in resolving addresses
909
       */
910
      public Builder setAddressesOrError(StatusOr<List<EquivalentAddressGroup>> addresses) {
911
        this.addresses = checkNotNull(addresses, "StatusOr addresses cannot be null.");
1✔
912
        return this;
1✔
913
      }
914

915
      /**
916
       * Sets the attributes for the addresses resolved by name resolution.  If unset,
917
       * {@link Attributes#EMPTY} will be used as a default.
918
       *
919
       * @since 1.21.0
920
       */
921
      public Builder setAttributes(Attributes attributes) {
922
        this.attributes = attributes;
1✔
923
        return this;
1✔
924
      }
925

926
      /**
927
       * Sets the Service Config parsed by {@link Args#getServiceConfigParser}.
928
       * This field is optional.
929
       *
930
       * @since 1.21.0
931
       */
932
      public Builder setServiceConfig(@Nullable ConfigOrError serviceConfig) {
933
        this.serviceConfig = serviceConfig;
1✔
934
        return this;
1✔
935
      }
936

937
      /**
938
       * Constructs a new {@link ResolutionResult} from this builder.
939
       *
940
       * @since 1.21.0
941
       */
942
      public ResolutionResult build() {
943
        return new ResolutionResult(addresses, attributes, serviceConfig);
1✔
944
      }
945
    }
946
  }
947

948
  /**
949
   * Represents either a successfully parsed service config, containing all necessary parts to be
950
   * later applied by the channel, or a Status containing the error encountered while parsing.
951
   *
952
   * @since 1.20.0
953
   */
954
  @ExperimentalApi("https://github.com/grpc/grpc-java/issues/1770")
1✔
955
  public static final class ConfigOrError {
956

957
    /**
958
     * Returns a {@link ConfigOrError} for the successfully parsed config.
959
     */
960
    public static ConfigOrError fromConfig(Object config) {
961
      return new ConfigOrError(config);
1✔
962
    }
963

964
    /**
965
     * Returns a {@link ConfigOrError} for the failure to parse the config.
966
     *
967
     * @param status a non-OK status
968
     */
969
    public static ConfigOrError fromError(Status status) {
970
      return new ConfigOrError(status);
1✔
971
    }
972

973
    private final Status status;
974
    private final Object config;
975

976
    private ConfigOrError(Object config) {
1✔
977
      this.config = checkNotNull(config, "config");
1✔
978
      this.status = null;
1✔
979
    }
1✔
980

981
    private ConfigOrError(Status status) {
1✔
982
      this.config = null;
1✔
983
      this.status = checkNotNull(status, "status");
1✔
984
      checkArgument(!status.isOk(), "cannot use OK status: %s", status);
1✔
985
    }
1✔
986

987
    /**
988
     * Returns config if exists, otherwise null.
989
     */
990
    @Nullable
991
    public Object getConfig() {
992
      return config;
1✔
993
    }
994

995
    /**
996
     * Returns error status if exists, otherwise null.
997
     */
998
    @Nullable
999
    public Status getError() {
1000
      return status;
1✔
1001
    }
1002

1003
    @Override
1004
    public boolean equals(Object o) {
1005
      if (this == o) {
1✔
1006
        return true;
×
1007
      }
1008
      if (o == null || getClass() != o.getClass()) {
1✔
1009
        return false;
×
1010
      }
1011
      ConfigOrError that = (ConfigOrError) o;
1✔
1012
      return Objects.equal(status, that.status) && Objects.equal(config, that.config);
1✔
1013
    }
1014

1015
    @Override
1016
    public int hashCode() {
1017
      return Objects.hashCode(status, config);
1✔
1018
    }
1019

1020
    @Override
1021
    public String toString() {
1022
      if (config != null) {
1✔
1023
        return MoreObjects.toStringHelper(this)
1✔
1024
            .add("config", config)
1✔
1025
            .toString();
1✔
1026
      } else {
1027
        assert status != null;
×
1028
        return MoreObjects.toStringHelper(this)
×
1029
            .add("error", status)
×
1030
            .toString();
×
1031
      }
1032
    }
1033
  }
1034

1035
  @Nullable
1036
  private static IdentityHashMap<Args.Key<?>, Object> cloneCustomArgs(
1037
          @Nullable IdentityHashMap<Args.Key<?>, Object> customArgs) {
1038
    return customArgs != null ? new IdentityHashMap<>(customArgs) : null;
1✔
1039
  }
1040
}
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