skip to content

How do you use getInstances(serviceId) and the ServiceInstance it returns? What information does a ServiceInstance carry?

level: middleimportance: should knowfreq 55%

answer

  1. getInstances -> List<ServiceInstance>
  2. host/port/uri/isSecure/metadata
  3. metadata map -> canary/zone routing
  4. empty list = transient, retry
  5. snapshot of cache, eventually consistent

basics

~10 s

Inject DiscoveryClient and call getInstances("my-service") to get a List<ServiceInstance>. Each ServiceInstance gives host, port, uri, isSecure, and a metadata map. You then pick one instance and build a URL to call it.

solid answer

~40 s

You inject the DiscoveryClient bean and call getInstances(serviceId), which returns a List<ServiceInstance> of the instances currently in the client's registry cache. Each ServiceInstance exposes getServiceId, getHost, getPort, isSecure, getUri (a ready-made scheme://host:port), and getMetadata (a String->String map holding registration metadata like zone, version, or weight). To call the service you'd normally hand the serviceId to a load-balanced client rather than iterate manually, but raw getInstances is useful for diagnostics, custom routing, blue-green/canary logic keyed on metadata, or health dashboards. Key caveats: the list is a snapshot of a locally-cached, eventually-consistent view, so it can contain a just-died instance or miss a just-started one; and an empty list means 'none known right now', which you must handle rather than assume the service is permanently gone.

code

java · 13 lines
java
@Component
class CanaryRouter {
    private final DiscoveryClient discoveryClient;
    CanaryRouter(DiscoveryClient discoveryClient) { this.discoveryClient = discoveryClient; }

    // Pick an instance whose metadata marks it as the canary version.
    Optional<URI> canaryUri(String serviceId) {
        return discoveryClient.getInstances(serviceId).stream()
            .filter(si -> "canary".equals(si.getMetadata().get("track")))
            .map(ServiceInstance::getUri)
            .findFirst();
    }
}

go deeper

for a junior

Should name getInstances and the basic ServiceInstance fields (host, port, uri).

for a middle

Should know the metadata map and that the list is an eventually-consistent snapshot.

for a senior

Should explain when to prefer LoadBalancer over manual iteration and how to handle empty/stale results.

for a principal

Should reason about metadata-driven routing (canary/zone) and health-filtering differences across registry backends.

## The API `DiscoveryClient` (interface `org.springframework.cloud.client.discovery.DiscoveryClient`) is a bean you inject. The main call is: ```java List<ServiceInstance> getInstances(String serviceId) ``` The `serviceId` is the **logical name** a service registered under (e.g. `order-service`) — case-insensitive in most registries, and by default derived from `spring.application.name`. ## ServiceInstance `org.springframework.cloud.client.ServiceInstance` describes one running instance: - `String getServiceId()` — the logical name. - `String getHost()` / `int getPort()` — network location. - `boolean isSecure()` — https vs http. - `URI getUri()` — convenience `scheme://host:port` built from the above. - `Map<String,String> getMetadata()` — arbitrary registration metadata. Registries let each instance publish key/value pairs (zone, region, version, weight, git-commit). This is the hook for **metadata-driven routing** (canary, zone affinity). - `String getInstanceId()` (default method) — a unique instance identifier when available. ## Typical usage For real traffic you almost never iterate `getInstances` and pick manually — you let **Spring Cloud LoadBalancer** do it via a `@LoadBalanced` `RestClient`/`WebClient`/`RestTemplate`, or via an OpenFeign client, all of which take a `http://order-service/...` URL and resolve+balance under the hood using the same discovery data. Direct `getInstances` shines for: - diagnostics / an admin 'what's registered' view, - custom routing logic (choose by metadata), - warming caches or health-checking, - tests. ## Edge cases and gotchas - **Eventual consistency:** the returned list comes from a **local cache** refreshed periodically from the registry, so it lags reality. Expect occasional dead instances (until the next heartbeat expiry) and delayed appearance of new ones. - **Empty list:** means 'no known instances now', which can be transient (registry not yet fetched, all instances mid-restart). Treat it as retryable, not a hard 404. - **Ordering not guaranteed:** don't assume the list order is stable or reflects load-balancing preference — that's the load balancer's concern. - **Health filtering varies by backend:** whether down/unhealthy instances are excluded depends on the underlying registry and its config (e.g. Eureka's instance status, Consul health checks). - **Snapshot semantics:** the list is a point-in-time copy; re-call for fresh data rather than caching it yourself.

  • If getInstances returns an empty list, does that mean the service is down?
    Not necessarily. It means no instances are known in the local cache right now — could be a not-yet-fetched registry, all instances restarting, or a registry hiccup. Treat it as retryable rather than a definitive 'gone'.
  • Why usually let LoadBalancer resolve serviceId instead of calling getInstances yourself?
    LoadBalancer handles instance selection, health/zone awareness, retries, and caching consistently. Manual getInstances loops duplicate that and are easy to get wrong; reserve them for diagnostics or metadata-based custom routing.

saying these in an interview costs you the question

  • Assuming the returned list is real-time accurate rather than a cached snapshot
  • Treating an empty list as a permanent failure
  • Believing list order encodes load-balancing priority
  • Storing the list and reusing it indefinitely instead of re-querying

context