# Netflix Spectator: a Java library for dimensional time series

> Netflix Spectator is an Apache 2.0 licensed Java library for instrumenting code so it records dimensional time series, with a minimal api artifact to compile against, pluggable registries for different backends, and an extension set for logging, cloud, garbage collection and server frameworks. The judgement is that its lasting contribution is the split between the interface you code against and the implementation you deploy, and that the weak point is a release line that is now two years old on the oldest supported runtime.

**Netflix/spectator** — Client library for collecting metrics.

- Repository: https://github.com/Netflix/spectator
- Stars: 768 · Forks: 195
- Language: Java
- License: Apache-2.0
- Published: 2026-10-21 · Updated: 2026-10-21 · Language: en
- Canonical page: https://hysenlabs.com/projects/netflix-spectator

## A client library whose main design choice is the interface split

Spectator is described in one line as a simple library for instrumenting code to record dimensional time series, and the word dimensional is the whole point. A metric in Spectator is not a name and a number, it is a name, a number and a set of tags, so the same measurement can be broken down by status code, by country or by exception type without a new instrument for every combination.

The structural decision is that the code you write depends on a separate api artifact, and the readme states the reason plainly. The api library provides the minimal interfaces for you to code against and to build test cases, and its only dependency is slf4j. Everything that actually ships the measurements, the registry implementations and the extensions, lives in other modules that the instrumented code never loads.

The practical effect is that a test can assert against an interface with a fake registry, and the choice of backend becomes a deployment decision rather than a compile time one. That is the design that has survived, and it is why the repository is shaped as a family of registries rather than as a single library with a pluggable point.

The documentation is on the Netflix Atlas wiki, and the API reference is published as javadoc on javadoc.io for the spectator-api artifact.

## What your build file actually declares

Adding Spectator to a project is a single dependency, and the readme gives the coordinate with a version placeholder:

```
com.netflix.spectator:spectator-api:${version}
```

That is the whole installation instruction, and it is the reason the api module is worth keeping minimal. The only transitive dependency is slf4j, which most Java services already have, and the interfaces are small enough that instrumenting a method does not pull in a framework.

The readme also points at a separate page for anyone running at Netflix with the standard platform, which is the internal integration path. That page is not a general deployment guide, and it is best to be clear about that, because the internal modules in the repository are the ones that assume company infrastructure and not much else is going to be usable elsewhere without modification.

For everyone else, the shape of the dependency is the point to take away. You add the api artifact to compile and test against, and then you add the module that matches your metrics backend at runtime. The api artifact on its own does nothing with the measurements, which is exactly what you want in a unit test.

## The Java baseline has moved three times

The requirements section is short and it records the history of the library in one place.

At runtime you need Java 8 or higher, and to build it you need Java 17 or higher. The readme then gives the historical lines: Java 8 or higher for Spectator 1.8.x or earlier, and Java 7 or higher for 0.27.x or earlier.

So the current line is built with a much newer compiler than it runs on, which is a common and sensible pattern for a library that wants to keep its runtime floor low while using modern language features internally. The risk is the one that comes with that pattern, where a build artefact compiled by a recent toolchain can pick up behaviour that differs on an old runtime, and the test matrix has to cover both ends.

For an adopter the practical question is which line to pin. A service on Java 8 can still use the current releases, since the runtime floor has not moved, and the older lines are still named in the documentation for anyone who needs to go further back.

The release history says the project is still moving in the 1.10 series, with v1.10.6 on 2026-08-19, v1.10.7 on 2026-09-09 and v1.10.8 on 2026-09-23, and the last push to the repository was on 2026-10-14.

## A timer, a counter and a distribution summary, tagged per request

The instrumentation example in the readme is the best description of how the library is used, because it shows a server that tracks four things: the number of requests received with dimensions for status code, country and exception type, the latency of handling them, a summary of the response sizes, and the number of active connections.

The instruments are created once, at construction, and held onto. The comment in the sample says the reference can be held onto to avoid additional lookup cost in critical paths, which is a performance argument rather than a style one, since a registry lookup on every request is a measurable cost.

The interesting part is how a dimension is attached. A base id is created for the request count, and the id is refined per request with the tags that matter:

```java
        final Id cntId = requestCountId
          .withTag("country", req.country())
          .withTag("status", res.status());
        registry.counter(cntId).increment();
```

The sample comments say the counter is then looked up in the registry, which should be fairly cheap, such as a lookup of the id object in a ConcurrentHashMap, but that it is more expensive than having a local variable set to the counter. The same pattern is repeated in the catch block with a status of exception and an extra tag for the exception class, so failures are counted in the same dimensional space as successes.

The latency measurement wraps the work in a runnable passed to the timer, and the response size goes into a distribution summary rather than a counter, because a size is a distribution and not a count.

## The gauge holds a weak reference so it cannot leak the object

The fourth instrument in the example is the one with the subtlety, and it deserves a close read.

A gauge that samples a value is registered like this:

```java
    registry.methodValue("server.numConnections", this, "getNumConnections");
```

The registry invokes the named method by reflection to get the value. The comment explains the part that matters: the registry keeps a weak reference to the object passed in, so registration will not prevent garbage collection of the server object.

That is a small design decision with a large consequence. A gauge that held a strong reference would keep the sampled object alive for the lifetime of the registry, which for a long-lived process means a leak of every object you ever registered a gauge on, and it would not look like a leak in a heap dump because the reference is held by a map you do not think of as a cache.

Using a weak reference moves the problem to the other side. The gauge stops reporting when the object is collected, which is the correct behaviour, but it means the instrument silently disappears if the only strong reference was the one you expected the registry to hold. The sample keeps a separate field for the registry itself and calls getNumConnections from the application, so the object is kept alive by the server itself rather than by its instrumentation.

It is a small thing, but it is the kind of detail that tells you the library was used in production long enough to have the failure modes written into the sample.

## Many registries, many extensions, one repository

The directory listing shows how wide the project is, and the names describe the design.

There is one registry module per backend, or per way of getting data out: spectator-reg-atlas, which is the Netflix Atlas path, spectator-reg-metrics3 and spectator-reg-metrics5 for the Netflix metrics stacks, spectator-reg-micrometer for Micrometer, spectator-reg-servo for the Netflix servo, spectator-reg-sidecar and spectator-reg-stateless for the newer sidecar and stateless models. Then there are the extensions, each wrapping something specific: aws and aws2 for cloud metrics, gc for garbage collection, ipc and ipcservlet for the inter-process transport, jvm for the virtual machine, log4j1 and log4j2 for the logging frameworks, placeholders for configuration substitution, sandbox for the test sandbox and spark for the Spark integration. On top sit spectator-agent, the Netflix agent, the spectator-nflx modules for the internal integration, spectator-perf for performance testing and spectator-web-spring for the Spring web layer.

The build is Gradle, with a dependencies.properties file pinning versions, and there is a codequality directory, a .netflixoss directory and an OSSMETADATA file, which are the artefacts of the Netflix open source release process rather than of the library itself. Two workflows run in continuous integration, snapshot.yml and release.yml, and the library is published to Maven Central.

Taken together, the shape of the repository is the argument for using it: the api you compile against is small and stable, and everything else is a module you choose.

## Conclusion

Spectator fits a Java service that wants to record metrics with tags and hand the transport to whatever backend the organisation already runs, because the api artifact has a single dependency and the registry implementations are separate modules you can swap. It is the wrong choice for a new greenfield project, since the last push was on 2026-10-14 with the newest tag at v1.10.8 from 2026-09-23 and the 1.8.x line still on Java 8, which means you are picking up a library whose ecosystem has moved on, and it is the wrong choice if you want the Netflix internals, since the platform specific modules are the ones that depend on company infrastructure. Before you adopt it, check the registry module that matches your backend, decide whether you want the Java 8 compatible line or the current one, and read the javadoc rather than the wiki, because the wiki has not been reorganised in years.

## FAQ

### What do I add to my build to use Spectator?

The api artifact, com.netflix.spectator:spectator-api with your version. Its only dependency is slf4j, and it provides the minimal interfaces to code against and to build test cases with. The module that ships the measurements to your backend is added separately at runtime.

### Which Java version does Spectator need?

Java 8 or higher at runtime and Java 17 or higher for building. The readme also records the older lines, Java 8 for 1.8.x and earlier and Java 7 for 0.27.x and earlier.

### How do I record a metric with dimensions?

Create a base id in the registry, refine it per event with withTag calls for each dimension such as country or status, and then look up the counter for that id and increment it. The example notes that this lookup should be cheap but is more expensive than a local variable reference, which is why the base ids are created once.

### Can a Spectator gauge keep my object alive?

No. A gauge registered with methodValue is sampled by reflection and the registry keeps a weak reference to the object, so registration will not prevent it from being garbage collected. The consequence is that the gauge stops reporting if nothing else holds a strong reference to the object.

## Sources

- [Issues](https://github.com/Netflix/spectator/issues)
- [License: Apache-2.0](https://github.com/Netflix/spectator/blob/main/LICENSE)
- [Netflix/spectator on GitHub](https://github.com/Netflix/spectator)
- [README](https://github.com/Netflix/spectator/blob/main/README.md)
- [Releases](https://github.com/Netflix/spectator/releases)

---

Hysen Labs editorial analysis, written from the project's own repository and release notes. Cite the canonical page: https://hysenlabs.com/projects/netflix-spectator
