{"slug": "how-to-instrument-spring-boot-applications-with-opentelemetry", "title": "How to Instrument Spring Boot Applications with OpenTelemetry", "summary": "SigNoz published a guide on instrumenting Spring Boot applications with OpenTelemetry, demonstrating how to set up a demo application, generate telemetry data, and visualize it in SigNoz. The tutorial covers OpenTelemetry's role in standardizing observability and avoiding vendor lock-in.", "body_md": "# How to Instrument Spring Boot Applications with OpenTelemetry\n\nSpring Boot has become the de facto standard web framework for Java, serving as the technical backbone for businesses of all sizes across all domains.\n\nStartups rely on it to quickly scaffold applications and—as all startups hope—scale to handle diverse workloads. Meanwhile, enterprises value its security, its proven reliability serving millions of users, and its ability to bridge modern, cloud-native architecture with legacy stacks.\n\nIt becomes critical to monitor your Spring Boot applications as they run in production, to understand system behaviour, diagnose errors, and prevent application downtime.\n\nThis is where OpenTelemetry comes into the picture.\n\nIn this article, we’ll start by briefly covering what OpenTelemetry is and why it matters. Then, we will dive into a hands-on demo to:\n\n- Set up a Spring Boot application instrumented with the OpenTelemetry Java Agent.\n- Generate live telemetry data, including distributed traces across service boundaries.\n- Visualize this data in SigNoz while dissecting the underlying code to understand exactly how our application behaves under the hood.\n\nWhat is OpenTelemetry?\n\n[OpenTelemetry](https://signoz.io/opentelemetry/) (OTel) is a Cloud Native Computing Foundation (CNCF) project that standardizes the way we instrument applications to generate and export telemetry data.\n\nBefore OTel, the three telemetry signals—traces, metrics, and logs—lived in isolated silos. OpenTelemetry standardizes telemetry generation and context propagation, which helps observability backends correlate data across signals.\n\nThis allows you to track exactly which request caused a specific log error or a spike in CPU usage, giving you a complete picture of your application's health.\n\n**Driven by Open Standards**\n\nOpenTelemetry follows a [specification-driven development](https://github.com/open-telemetry/opentelemetry-specification?tab=readme-ov-file) model.\n\nThese open standards mean **there is no vendor lock-in.** If your application emits standard OTel data, any compatible backend can process and visualize it.\n\nNow that we understand why OpenTelemetry is important to the observability landscape, let’s get hands-on with our demo application, generate some live traffic, and see OpenTelemetry in action.\n\nSetting Up the Spring Boot Demo Application\n\nLet’s go ahead and set up the demo application that we’ve prepared to showcase OpenTelemetry instrumentation with Spring Boot.\n\nPrerequisites\n\nBefore cloning the demo application repo, ensure you have:\n\n- Java 21 installed. (\n[Download here](https://www.oracle.com/in/java/technologies/downloads/#java21)). - Maven version 3.6.3 or later (\n[Download here](https://maven.apache.org/download.cgi)). - uv installed (\n[Download here](https://docs.astral.sh/uv/getting-started/installation/#installation-methods)). - A\n[SigNoz Cloud account](https://signoz.io/teams/)for visualizing the telemetry data.\n\n** uv** is required to run the Python script that emulates an upstream service to showcase\n\n[trace context propagation](https://signoz.io/blog/context-propagation-in-distributed-tracing/).\n\n**Setting up SigNoz**\n\nSigNoz is an all-in-one, OpenTelemetry-native observability platform for traces, metrics, and logs.\n\n[Sign up](https://signoz.io/teams/)for a free SigNoz Cloud account.[Follow this guide](https://signoz.io/docs/ingestion/signoz-cloud/keys/)to create ingestion keys for your account.- Ensure the region and ingestion key values are readily accessible for the following steps.\n\nOnce done, you’re ready to set up the demo application.\n\nRunning the Demo Application\n\nClone the SigNoz Examples repository and navigate to the application folder:\n\n```\ngit clone https://github.com/SigNoz/examples.git\ncd examples/java/opentelemetry-spring-boot-demo\n```\n\nNext, ensure that you set the OpenTelemetry exporter endpoint and the SigNoz ingestion key environment variables.\n\n```\nexport OTEL_EXPORTER_OTLP_ENDPOINT=\"https://ingest.<your-region>.signoz.cloud:443\"\nexport SIGNOZ_INGESTION_KEY=\"<your-ingestion-key>\"\n```\n\nWe have included a [Makefile](https://github.com/SigNoz/examples/blob/main/java/opentelemetry-spring-boot-demo/Makefile) to simplify the setup. Run the `make run`\n\ncommand, which will download the [OpenTelemetry Auto-Instrumentation Agent](https://github.com/open-telemetry/opentelemetry-java-instrumentation) JAR file, and start the Spring Boot application—with the agent attached—on port 8085.\n\nMake a curl request or visit the `http://127.0.0.1:8085`\n\nURL to validate that the web server is up and running:\n\n```\n❯ curl -i http://127.0.0.1:8085  \nHTTP/1.1 200 \nContent-Type: text/plain;charset=UTF-8\nContent-Length: 13\nDate: Tue, 31 Mar 2026 06:20:00 GMT\n\nHello, World!\n```\n\nGenerating Data for Visualization\n\nTo ensure you have enough data to experiment with and understand how OpenTelemetry works, we have prepared a simple load generator script that calls endpoints from our application.\n\nThe script also calls an undefined endpoint and the fibonacci endpoint with invalid data. This helps us visualize telemetry beyond just the happy path.\n\nRun the script in a separate terminal instance:\n\n```\nchmod +x ./scripts/load_gen.sh\n./scripts/load_gen.sh\n```\n\nNext, start the Python script to generate data for the external API. This script calls the Spring Boot `/external`\n\nendpoint, which in turn calls an `httpbin`\n\nendpoint.\n\n```\nOTEL_SERVICE_NAME=\"py-springboot-client\" \\\nOTEL_EXPORTER_OTLP_ENDPOINT=\"https://ingest.<your-region>.signoz.cloud:443\" \\\nOTEL_EXPORTER_OTLP_HEADERS=\"signoz-ingestion-key=<your-signoz-key>\" \\\nuv run \\\n  --with opentelemetry-distro \\\n  --with opentelemetry-exporter-otlp \\\n  --with opentelemetry-instrumentation-requests \\\n  opentelemetry-instrument python scripts/python_client.py\n```\n\nYou will be prompted to enter the number of requests you’d like to make to the application. For now, you can just enter ** 1**. To generate more data points, enter a large value like\n\n`50`\n\nand let the script run.Notice the `traceparent`\n\nheader in that response? That contains the `trace_id`\n\noriginally generated by our Python script. It successfully passed through our Spring Boot application and made its way to the external `httpbin`\n\nservice.\n\nWith telemetry now actively flowing into your SigNoz dashboard, let’s look under the hood.\n\nAnatomy of the OpenTelemetry Spring Boot Demo Application\n\nIt’s vital that you understand the various nuances of the demo application. Wrapping your head around exactly how our application captures spans, generates metrics, and propagates that `traceparent`\n\ncontext will prepare you for implementing OpenTelemetry in your own applications.\n\nWe will use the [OpenTelemetry Java agent](https://signoz.io/opentelemetry/java-agent/) for this guide, as we believe it’s the practical default for standard JVM deployments. It requires minimal code changes, supports a broad range of Java libraries and frameworks, and keeps your telemetry model aligned with OpenTelemetry across the rest of your distributed system.\n\nFor Spring Boot native image applications, use the OpenTelemetry Spring Boot starter instead, since the Java agent does not generally work there.\n\nDissecting the Included Makefile\n\nOur Makefile’s first major task is to download the agent JAR file into the `agent/`\n\ndirectory at the `AGENT_JAR`\n\npath.\n\n```\nAGENT_JAR := agent/opentelemetry-javaagent.jar\n\ndownload-agent: ## Download the OTel Java Agent JAR (run once)\n\t@bash scripts/download_agent.sh\n\n## associate the AGENT_JAR file's existence with the download-agent target\n$(AGENT_JAR): download-agent\n```\n\nTo ensure that the JAR file exists before the application starts, the `run`\n\ntarget defines it as a dependency, which ensures `download-agent`\n\nalways runs first if there is no file at `AGENT_JAR`\n\n.\n\n```\nrun: $(AGENT_JAR) ## Start the demo app with the OTel Java Agent attached\n\t@: $${OTEL_EXPORTER_OTLP_ENDPOINT:?'OTEL_EXPORTER_OTLP_ENDPOINT is not set. Example: https://ingest.us.signoz.cloud:443'}\n\t@: $${SIGNOZ_INGESTION_KEY:?'SIGNOZ_INGESTION_KEY is not set. Get it from your SigNoz Cloud ingestion keys page.'}\n\tOTEL_SERVICE_NAME=opentelemetry-spring-boot-demo \\\n\tOTEL_EXPORTER_OTLP_ENDPOINT=$(OTEL_EXPORTER_OTLP_ENDPOINT) \\\n\tOTEL_EXPORTER_OTLP_HEADERS=\"signoz-ingestion-key=$(SIGNOZ_INGESTION_KEY)\" \\\n\tOTEL_RESOURCE_ATTRIBUTES=\"service.version=0.1.0,deployment.environment=dev\" \\\n\tOTEL_METRIC_EXPORT_INTERVAL=10000 \\\n\tOTEL_JAVA_DISABLED_RESOURCE_PROVIDERS=io.opentelemetry.instrumentation.resources.ProcessResourceProvider \\\n\tOTEL_INSTRUMENTATION_HTTP_CLIENT_EMIT_EXPERIMENTAL_TELEMETRY=true \\\n\tOTEL_INSTRUMENTATION_HTTP_SERVER_EMIT_EXPERIMENTAL_TELEMETRY=true \\\n\tmvn spring-boot:run \\\n\t  -Dspring-boot.run.jvmArguments=\"-javaagent:$(AGENT_JAR)\"\n```\n\nThe `OTEL_METRIC_EXPORT_INTERVAL=10000`\n\nvariable defines a regular 10-second interval for exporting metrics. This is a lower value than the default, and will help us capture application trends faster for this demo.\n\nTo learn how to better configure this interval and other properties, check out our detailed guide on [OpenTelemetry environment variables](https://signoz.io/blog/otel-environment-variables/).\n\nWe also have several Java-specific variables, such as:\n\n`OTEL_JAVA_DISABLED_RESOURCE_PROVIDERS`\n\nwhich disables the overly verbose.`ProcessResourceProvider`\n\n`OTEL_INSTRUMENTATION_HTTP_*`\n\nexperimental variables that enable the collection of certain telemetry data attributes and metrics undergoing active development.\n\nExcluding verbose providers and attributes like the `ProcessResourceProvider`\n\nhelps reduce telemetry payload sizes. Not only does this reduce CPU and network overhead within the instrumented application, but it also helps control observability bills.\n\nAlternatively, you can also modify or drop telemetry at the [OTel Collector](https://signoz.io/blog/opentelemetry-collector-complete-guide/) level.\n\nExporting Traces, Metrics, and Logs\n\nOn application startup, the Java agent reads these environment variables and dynamically configures its export pipeline.\n\nWithout requiring any additional code, the agent automatically captures all generated telemetry and routes it directly to your specified OpenTelemetry backend.\n\nYou can configure the agent’s behaviour by modifying the environment variables in the Makefile’s `run`\n\ntarget.\n\nFor example, to capture detailed client and server data, we’ve included the Java-specific environment variables: `OTEL_INSTRUMENTATION_HTTP_CLIENT_EMIT_EXPERIMENTAL_TELEMETRY`\n\nand `OTEL_INSTRUMENTATION_HTTP_SERVER_EMIT_EXPERIMENTAL_TELEMETRY`\n\n.\n\nA common scenario when debugging telemetry export failures is to visualize the raw telemetry data locally. You can do so by setting the [per-signal exporter environment variable](https://signoz.io/blog/otel-environment-variables/#selecting-exporters-per-signal) to `otlp,console`\n\n.\n\nCheck out the OpenTelemetry [Java agent's instrumentation configuration](https://opentelemetry.io/docs/zero-code/java/agent/instrumentation/) documentation to find all possible configuration options.\n\nUnderstanding OpenTelemetry: Custom Logic, System Metrics, and Context\n\nThe application features an index (served at `/`\n\n), `/fibonacci`\n\nand `/external`\n\nendpoints. It also exports a custom business metric to our OTel backend. Let’s start understanding each aspect one-by-one.\n\n**Manual Instrumentation: Spans**\n\nThe fibonacci endpoint calculates Fibonacci sequences of numbers between 0 and 92, using a random sleep duration to mock the behaviour of complex processing logic.\n\nWe have restricted the maximum input value to 92 to prevent `long`\n\ninteger overflows and resource exhaustion.\n\nBy manually instrumenting the `fibonacci.compute`\n\nfunction, we encapsulate the actual calculation logic and measure the exact time taken, and log the input and output values as [span attributes](https://signoz.io/blog/opentelemetry-spans/).\n\nWe also record the output with our metrics service, we’ll go over what it does shortly.\n\n```\n@WithSpan(\"fibonacci.compute\")\npublic long compute(@SpanAttribute(\"fibonacci.number\") int n) {\n    ...\n    long result = fib(n);\n    metricsService.recordFibonacciCalculation(result);\n    // manually set a span attribute for the result\n    Span.current().setAttribute(\"fibonacci.result\", result);\n    log.debug(\"fibonacci({}) = {}\", n, result);\n    return result;\n}\n```\n\nVisualizing the span in the detail view, we can see the function name, the input number, and the result calculation alongside the Gantt chart.\n\nAs part of the auto-instrumentation, each log record gets injected with the current trace context. This means we can directly access log statements for the entire trace directly from the same view.\n\nClick on the parent `POST /fibonacci`\n\nspan and scroll to the top on the right panel. Then, click on the `Logs`\n\nbutton under the `Related Signals`\n\nsection. You will now see a split view listing all the log statements generated during that trace.\n\nYou can open the entries in the Logs Explorer view if you wish to inspect them in detail.\n\n**Manual Instrumentation: Metrics**\n\nCustom metrics allow you to draw insights from application behaviour that is not covered as part of the agent’s auto-instrumentation.\n\nHere, we have implemented an `app.fibonacci.calculations`\n\ncounter that tracks successful Fibonacci calculations and “tags” them based on the result value band.\n\n```\n@PostConstruct\npublic void init() {\n    Meter meter = GlobalOpenTelemetry.getMeter(\"opentelemetry-spring-boot-demo\");\n\n    fibonacciCalculations = meter\n            .counterBuilder(\"app.fibonacci.calculations\")\n            .setDescription(\"Count of successful Fibonacci calculations by result band\")\n            .setUnit(\"1\")\n            .build();\n}\n\npublic void recordFibonacciCalculation(long result) {\n    fibonacciCalculations.add(1, Attributes.of(\n            FIBONACCI_RESULT_BAND, toResultBand(result)));\n}\n\nprivate String toResultBand(long result) {\n    if (result < 10) {\n        return \"single_digit\";\n    }\n    if (result < 100) {\n        return \"double_digit\";\n    }\n    if (result < 1_000_000) {\n        return \"medium\";\n    }\n    if (result < 1_000_000_000_000L) {\n        return \"large\";\n    }\n    return \"huge\";\n}\n```\n\nUsing this metric, you can quickly understand the data patterns related to each result band. Here, we can see that single digit calculations are the most common, before inputs skew towards larger number sets.\n\nAny significant change in these data patterns would mean a change in the user behaviour. In the real world, you could use this data for capacity planning or configuring dynamic rate limits per-band.\n\n**Monitoring System Health**\n\nMetrics are a foundational pillar of observability—and as seen above, for good reason. By tracking key metrics, you can understand overall system health at a glance, or derive deeper insights to understand how your application behaves at the different stages of its lifecycle.\n\nBeyond the custom Fibonacci counter, the OTel agent provides a massive baseline of system metrics out of the box—such as JVM memory usage, garbage collection times, and thread counts.\n\nToday, we’ll focus on the core metric that every team understands and deeply cares about: **request duration** percentiles.\nLet’s see what we find after our load generator has been running for some time.\n\nDuring peak load, we can see that the invalid endpoint (`/**`\n\nwhich returns a 404), the index, and the fibonacci endpoints all maintain consistent latencies. The external endpoint has a dip, but otherwise stays above the 1.5-second mark.\n\nIn a production environment, an engineer would be tasked with investigating this latency behaviour. Is the downstream service rate-limiting us? Are we exhausting web server thread counts? And so on.\n\n**Trace Context Propagation**\n\nAs we discussed briefly earlier, the Python script generates distributed traces by calling the `/external`\n\nendpoint, which in turn calls [httpbin.org/anything](http://httpbin.org/anything).\n\nBecause we also instrumented the Python code using the OpenTelemetry SDK (`opentelemetry-instrument`\n\n), all of its outgoing API calls to our Spring Boot application include the `traceparent`\n\nheader, which has the `trace_id`\n\nand `span_id`\n\n.\n\nOn receiving this request, our application parses the header and integrates these IDs into its own context.\n\nNext, when our Spring Boot handler function calls the `httpbin`\n\nservice, our application attaches its own trace context *to that outgoing request*. This chaining mechanism ensures that all operations are recorded as part of the exact same request.\n\n```\n@GetMapping(\"/external\")\npublic ResponseEntity<JsonNode> external() {\n    try {\n        String body = restClient.get()\n                .uri(HTTPBIN_PATH)\n                .retrieve()\n                .body(String.class);\n\n        JsonNode httpbinResponse = objectMapper.readTree(body);\n        JsonNode response = objectMapper.createObjectNode()\n                .put(\"note\", \"This endpoint calls httpbin with propagated trace context.\")\n                .set(\"httpbin_response\", httpbinResponse);\n\n        return ResponseEntity.ok(response);\n\n    } catch (Exception e) {\n        Span.current().setStatus(StatusCode.ERROR, e.getMessage());\n        Span.current().setAttribute(\"error.type\", e.getClass().getSimpleName());\n        throw new IllegalStateException(\"httpbin request failed\", e);\n    }\n}\n```\n\nThe resulting distributed trace captures the flow of data across three distinct boundaries: Python script → Spring Boot application → httpbin.\n\nThis visualization ensures you always have complete context of the data flow and the complex interactions among multiple components for any given request.\n\nWhat's Next with OpenTelemetry & Spring Boot\n\nBy now, you’ve instrumented a Spring Boot application with the OpenTelemetry Java agent, generated traces and metrics, and verified trace propagation across services.\n\nYou are now ready to integrate OpenTelemetry with your Spring Boot stack. Start with auto-instrumentation, then identify key business logic flows to manually instrument.\n\nFurther, build custom dashboards and configure alerts to track errors and unexpected application behaviour.\n\nExplore Spring Boot Telemetry from Your Coding Agent\n\nOnce your Spring Boot application is sending telemetry to SigNoz, you can also investigate it directly from your coding workflow. As an optional next step, connect the [SigNoz MCP Server](https://signoz.io/docs/ai/signoz-mcp-server/) and ask about slow endpoints, JVM bottlenecks, or trace patterns in natural language without leaving your IDE. For a broader overview, see [Agent Native Observability](https://signoz.io/agent-native-observability/).\n\n[SigNoz](https://signoz.io/) is an all-in-one, OpenTelemetry-native platform for traces, metrics, and logs. If you’re interested in trying out SigNoz for your applications, [sign up](https://signoz.io/teams/) for a 30-day free trial (no credit card required).", "url": "https://wpnews.pro/news/how-to-instrument-spring-boot-applications-with-opentelemetry", "canonical_source": "https://signoz.io/blog/opentelemetry-spring-boot", "published_at": "2026-06-16 00:00:00+00:00", "updated_at": "2026-06-17 04:54:57.067099+00:00", "lang": "en", "topics": ["developer-tools"], "entities": ["SigNoz", "OpenTelemetry", "Spring Boot", "Cloud Native Computing Foundation", "Java"], "alternates": {"html": "https://wpnews.pro/news/how-to-instrument-spring-boot-applications-with-opentelemetry", "markdown": "https://wpnews.pro/news/how-to-instrument-spring-boot-applications-with-opentelemetry.md", "text": "https://wpnews.pro/news/how-to-instrument-spring-boot-applications-with-opentelemetry.txt", "jsonld": "https://wpnews.pro/news/how-to-instrument-spring-boot-applications-with-opentelemetry.jsonld"}}