Why observability needs a standard
Logs are useful, but they are not enough once a request crosses process boundaries. When a Spring Boot API talks to a database, a queue, or another service, you need a way to follow that work without reconstructing it by hand.
OpenTelemetry gives that work a shared language. It is not a vendor dashboard. It is a set of APIs, SDKs, and conventions for producing traces, metrics, and logs that can be exported to the backend you choose.
Traces, spans, and context
A trace represents one unit of work, such as an incoming HTTP request.
A span is a step inside that work: the controller method, a database call, an outgoing HTTP client.
Context propagation is the part that makes this useful. Each span carries identifiers so the next step can attach itself to the same trace. Without that, you only have disconnected events.
What to instrument first
Start with signals that answer operational questions:
- Request rate and latency
- Error rate
- Runtime health, such as memory and thread usage
- Dependency behavior, especially database calls
Do not instrument everything on day one. Unused telemetry is noise, and noise makes dashboards useless.
A Java-shaped setup
In a Spring Boot service, OpenTelemetry usually sits beside the application as an SDK or Java agent. The service produces spans and metrics. A collector or exporter sends them onward. Prometheus can scrape metrics. Grafana can visualize both metrics and traces depending on the backend you connect.
The important design choice is this: the application should emit useful signals even when the visualization stack changes later.
Local first
If telemetry only exists in production, you will not practice reading it. Running Prometheus and Grafana next to the service in Docker makes observability part of development.
That is the same reason tests belong in the repository. You cannot improve what you never inspect.
What OpenTelemetry is not
It is not a replacement for good failure handling.
It is not a reason to log every field of every payload.
It is a way to make runtime behavior visible with a portable contract.
Once that contract exists, choosing Grafana, a commercial APM, or another backend becomes an operations decision instead of an application rewrite.
Related articles

Building a content-driven Next.js UI
How to keep React components reusable by pushing portfolio content behind a data access layer.

From commit to container with GitHub Actions
A compact CI path for Java services: build, test, package with Docker, and keep secrets out of the repository.
