Your Spring Boot application is running on Amazon ECS. API requests are succeeding, business operations are executing, and the application logs look normal.
But when you open Amazon CloudWatch, the custom metrics you expected to see are missing.
You check the dashboard. No data.
You check the metric namespace. Nothing appears.
You wait a few minutes and refresh. Still nothing.
Where should you start debugging?
The problem could be in the application, Micrometer configuration, metric publishing, AWS permissions, network connectivity, or the way you are querying CloudWatch.
In this article, we'll build a small example and follow the complete path from recording a metric in Spring Boot to verifying it in CloudWatch.
By the end, you'll have a systematic troubleshooting process you can reuse in your own applications.
1. Understand the complete metrics flow
Before changing any configuration, understand how a custom application metric reaches CloudWatch.
Spring Boot Application
|
v
Business Operation
|
v
Micrometer Counter / Timer
|
v
MeterRegistry
|
v
CloudWatch Registry
|
v
CloudWatch PutMetricData API
|
v
Amazon CloudWatch Metrics
|
+------> Dashboard
|
+------> Alarm
Each component has a different responsibility.
- Business operation: The event you want to measure, such as a successfully created order.
- Micrometer: Provides a consistent API for recording application metrics.
- MeterRegistry: Holds and manages the meters registered by your application.
- CloudWatch registry: Exports supported Micrometer measurements to AWS.
- CloudWatch: Stores the published metric data and makes it available for querying, visualization, and alarms.
A metric being recorded in Java does not automatically mean it has been published to CloudWatch.
Similarly, a metric existing in CloudWatch does not automatically mean your dashboard is configured to display it.
This distinction is the foundation of the debugging process.
2. Understand SimpleMeterRegistry versus CloudWatchMeterRegistry
One of the first things to investigate is the MeterRegistry used by your application.
Micrometer provides different registry implementations for different purposes.
SimpleMeterRegistry
SimpleMeterRegistry stores the latest values of meters in memory. It does not export those measurements to an external monitoring system.
For example:
SimpleMeterRegistry registry = new SimpleMeterRegistry();
Counter counter = Counter.builder("orders.created")
.register(registry);
counter.increment();
System.out.println(counter.count());
The counter value increases locally, but this code does not send the metric to CloudWatch.
This registry is useful for simple tests and local experimentation.
CloudWatchMeterRegistry
CloudWatchMeterRegistry is the Micrometer registry implementation for publishing supported metrics to Amazon CloudWatch.
However, adding a dependency alone is not sufficient in every Spring application. You also need the appropriate registry configuration and AWS client credentials, permissions, and Region.
What happens in Spring Boot?
Spring Boot Actuator integrates with Micrometer and can configure a composite registry containing the available registry implementations.
When a CloudWatch-compatible registry is properly configured, metrics recorded through the injected MeterRegistry can be exported through that registry.
If your application is only recording to an in-memory registry, there is no CloudWatch publishing path.
Documentation:
3. Create a simple Spring Boot example
Let's create a small example that records a custom metric whenever a demo order operation succeeds.
The example deliberately avoids a database so that we can isolate metric recording and publishing from business logic.
Record the metric
import io.micrometer.core.instrument.Counter;
import io.micrometer.core.instrument.MeterRegistry;
import org.springframework.stereotype.Service;
@Service
public class OrderService {
private final Counter ordersCreated;
public OrderService(MeterRegistry registry) {
this.ordersCreated = Counter.builder("orders.created")
.description("Number of successful demo order operations")
.register(registry);
}
public void createOrder() {
// Replace this with the actual business operation.
ordersCreated.increment();
}
}
Here, we register a counter named orders.created.
Every time createOrder() completes its intended operation, the counter increments.
In a real application, the metric should reflect a clearly defined business event. If the operation involves a database transaction, be careful not to count an order as successfully committed before the transaction actually commits.
Expose a demo endpoint
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class OrderController {
private final OrderService orderService;
public OrderController(OrderService orderService) {
this.orderService = orderService;
}
@PostMapping("/orders/demo")
public String createOrder() {
orderService.createOrder();
return "Demo order operation completed";
}
}
After starting the application, send a POST request to:
POST /orders/demo
For example, using curl:
curl -X POST http://localhost:8080/orders/demo
Each successful request invokes the service and increments the counter.
At this point, we have demonstrated metric recording. We have not yet proved that the metric reaches CloudWatch.
4. Configure CloudWatch publishing
For this example, assume you're using:
- Java and Spring Boot 3.x.
- A compatible Spring Cloud AWS 3.x release.
- Micrometer's CloudWatch registry.
- AWS SDK for Java 2.x through the supported integration.
Use compatible dependency versions and the Spring Cloud AWS Bill of Materials (BOM). Do not mix incompatible major versions just to make the dependencies resolve.
Add the dependencies
For Maven, the relevant dependencies include the following:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<dependency>
<groupId>io.awspring.cloud</groupId>
<artifactId>spring-cloud-aws-starter</artifactId>
</dependency>
<dependency>
<groupId>io.micrometer</groupId>
<artifactId>micrometer-registry-cloudwatch2</artifactId>
</dependency>
Import the compatible Spring Cloud AWS BOM in dependencyManagement and use Spring Boot's dependency management for the versions they manage.
For example, the Spring Cloud AWS BOM has this structure:
<dependencyManagement>
<dependencies>
<dependency>
<groupId>io.awspring.cloud</groupId>
<artifactId>spring-cloud-aws-dependencies</artifactId>
<version>${spring-cloud-aws.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
Define spring-cloud-aws.version as a release compatible with your Spring Boot version. The placeholder above is intentional; select the version using the official compatibility guidance.
Spring Cloud AWS provides the Spring Boot auto-configuration for the Micrometer CloudWatch integration.
References:
Configure application.yml
For Spring Cloud AWS 3.x, a configuration example is:
spring:
cloud:
aws:
cloudwatch:
enabled: true
region: us-east-1
management:
cloudwatch:
metrics:
export:
namespace: Demo/OrderService
step: 1m
Replace us-east-1 with your AWS Region.
The namespace Demo/OrderService is an example. Choose a namespace that identifies your application or service.
The step: 1m setting configures the publishing interval to one minute. Allow additional time for the data to become available after publishing.
Important: Spring Cloud AWS 3.x uses management.cloudwatch.metrics.export.* properties. Older Spring Cloud AWS versions use different property names. If you're using a different release family, consult the documentation for that exact version instead of copying this configuration unchanged.
5. Verify that the application records the metric
Before investigating AWS, verify that the application actually records the metric.
Spring Boot Actuator provides a metrics endpoint that can help with this check.
For local debugging, you can expose the health and metrics endpoints in application.yml:
management:
endpoints:
web:
exposure:
include: health,metrics
If your configuration already contains a management section, merge the settings rather than replacing unrelated properties.
Restart the application and invoke the demo endpoint several times.
Then query:
curl http://localhost:8080/actuator/metrics/orders.created
The response should contain information about the registered meter and its measurements. After invoking the endpoint multiple times, check the counter's current value.
If the metric is missing from Actuator, investigate the application first:
- Is the relevant service method actually being called?
- Is the counter registered under the name you expect?
- Is the application using the
MeterRegistryinjected by Spring? - Is a different registry or metric filter involved?
- Did the application start with the expected configuration?
If the metric is visible through Actuator, you've established that the application can record it locally. You still need to verify external publishing separately.
Security note: Do not expose Actuator metrics publicly without appropriate access controls. Keep diagnostic endpoints internal or restrict them to authorized users.
6. Check AWS credentials and IAM permissions
A CloudWatch registry needs AWS credentials with permission to publish metric data.
When the application runs on Amazon ECS, distinguish between the two IAM roles.
ECS task role
The task role supplies AWS permissions to application code running inside the task.
For direct CloudWatch metric publishing by the application, this is generally the role that needs the cloudwatch:PutMetricData permission.
ECS task execution role
The execution role is used by the ECS agent for tasks such as pulling container images from Amazon ECR and sending container logs through configured logging integrations.
It is not automatically the identity used by your application for CloudWatch metric publishing.
Granting a permission to the execution role will not fix missing application metrics if the application is using the task role instead.
Example IAM policy
For a dedicated custom namespace, a policy can be restricted using CloudWatch's namespace condition key:
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": "cloudwatch:PutMetricData",
"Resource": "*",
"Condition": {
"StringEquals": {
"cloudwatch:namespace": "Demo/OrderService"
}
}
}
]
}
This is an example policy for the namespace used in this tutorial. Adjust it to your requirements and verify any additional organizational policies.
CloudWatch metric publishing does not support resource-level restriction in the usual way, so Resource: "*" is used here with a namespace condition.
Avoid hardcoding AWS access keys in source code, container images, or configuration files. Use the ECS task role and the AWS SDK's supported credential provider chain.
If you receive an authorization error, check:
- Which IAM role the task is actually using.
- Whether that role has
cloudwatch:PutMetricData. - Whether a namespace condition matches the configured namespace.
- Whether an AWS Organizations service control policy, permissions boundary, or other policy denies the request.
Reference: IAM roles for Amazon ECS tasks.
7. Check network connectivity from ECS
Correct IAM permissions are not enough if the application cannot reach the CloudWatch API.
This matters especially when an ECS task runs in a private subnet.
Check whether the task has a supported network path to the CloudWatch monitoring API. Depending on your architecture, this might be through a NAT gateway or an appropriate VPC interface endpoint.
Also verify:
- DNS resolution.
- Security group egress rules.
- Network ACL rules.
- VPC endpoint configuration and policies, if applicable.
- Application logs for connection timeouts or endpoint errors.
Do not assume that access to CloudWatch Logs proves that access to the CloudWatch metrics API is working. They are different API endpoints and may have different network paths and permissions.
8. Check the CloudWatch Region and namespace
Suppose your application publishes to us-east-1, but you're viewing CloudWatch in eu-west-1.
The metric will not appear in the Region you're viewing.
Verify the following values:
| Setting | What to verify |
|---|---|
| AWS Region | The Region configured for the CloudWatch client |
| Console Region | The Region selected in the CloudWatch console |
| Namespace | The configured custom namespace |
| Metric name | The name registered and exported by the application |
| Dimensions | The dimension names and values associated with the published series |
Custom metrics are identified by their namespace, metric name, and dimensions.
For example, a metric with Environment=dev is a different time series from one with Environment=prod.
If you add dimensions through registry configuration, filters, or instrumentation, use the correct dimension combination when querying the metric.
Avoid high-cardinality dimensions such as user IDs, order IDs, request IDs, or arbitrary URLs. Every unique combination can create additional metric series and increase monitoring costs.
9. Verify that CloudWatch has received the metric
Once the application has recorded the metric and the registry has had time to publish it, query CloudWatch directly.
You can use the AWS CLI:
aws cloudwatch list-metrics \
--namespace "Demo/OrderService" \
--metric-name "orders.created" \
--region us-east-1
Replace the namespace, metric name, and Region with your actual values.
This command helps determine whether CloudWatch lists the metric.
What if the command returns no results?
Do not immediately conclude that publishing failed.
AWS documents that a newly created metric can take up to 15 minutes to appear through ListMetrics. Statistics may be queryable sooner using GetMetricData or GetMetricStatistics.
Also note that ListMetrics does not return metrics that have not reported data in the past two weeks.
Therefore, check the following:
- Did the publishing interval elapse?
- Did the application log any publishing errors?
- Are the namespace and Region correct?
- Is the metric name correct?
- Are you querying from the correct AWS account?
- Does the metric have dimensions that you haven't accounted for?
You can also inspect metric statistics with a command such as:
aws cloudwatch get-metric-statistics \
--namespace "Demo/OrderService" \
--metric-name "orders.created" \
--start-time 2026-11-01T00:00:00Z \
--end-time 2026-11-01T01:00:00Z \
--period 60 \
--statistics Sum \
--region us-east-1
Important: The dates above are illustrative. Replace them with a UTC time window that covers your actual test. If your metric has dimensions, include the exact dimension combination in the query.
The Sum statistic is appropriate only when it matches the meaning of the published datapoints. For counters exported by a registry, inspect the registry's publishing semantics and actual CloudWatch datapoints rather than assuming that summing cumulative values always gives the number of events.
References:
10. The metric exists, but the dashboard still shows no data
A CloudWatch dashboard visualizes the metric series selected in its widget. It does not create the metric itself.
If the metric can be queried but the dashboard is blank, inspect the dashboard configuration.
Check the selected metric
Verify that the widget points to the correct:
- Namespace.
- Metric name.
- Dimension combination.
- Region.
Check the time range
The dashboard might be displaying a time range that does not contain your test requests.
Choose a range that includes the time when you invoked the demo endpoint and when the registry published its data.
Check the period and statistic
The metric's publishing interval and the widget's aggregation period are related but are not necessarily the same setting.
Select a period that makes sense for the metric's publishing frequency and the time range being displayed.
For event counters, use a statistic that correctly represents the published values. For latency measurements, choose a statistic appropriate for the question you're trying to answer, such as average or a supported percentile.
Check dimensions
If the metric has dimensions, the dashboard must query the correct series. A mismatch in dimension values can make a valid metric appear to be missing.
The quickest diagnostic sequence is:
- Confirm that the metric is visible through Actuator.
- Confirm that CloudWatch has received it.
- Query the metric's data.
- Only then troubleshoot the dashboard widget.
This separates metric collection problems from visualization problems.
11. What about CloudWatch alarms?
After confirming that a metric is published correctly, you can create an alarm to monitor it.
However, a valid metric does not guarantee that an alarm will transition to ALARM.
Check the following:
- The alarm references the correct namespace, metric name, and dimensions.
- The statistic matches the meaning of the published measurements.
- The threshold and comparison operator are correct.
- The period and evaluation periods match the intended detection behavior.
- Missing-data treatment is appropriate for the metric.
- The alarm has enough data to evaluate its condition.
- Any notification action is configured and enabled.
For example, a counter that records failed business operations can be useful for monitoring errors. But the alarm threshold should reflect the expected volume and the way the metric is aggregated.
An alarm in INSUFFICIENT_DATA may not have enough datapoints to evaluate its condition. Missing data should not automatically be interpreted as evidence that the application is healthy.
For a low-traffic application, the absence of events can be normal. For a continuously running service, the absence of expected telemetry may itself warrant investigation.
12. Common symptoms and their likely causes
Use this table as a quick reference when debugging a real service.
| Symptom | What to investigate first |
|---|---|
| Application starts, but no custom metrics appear | Registry dependency, initialization, and publishing configuration |
| Actuator shows the metric, but CloudWatch does not | CloudWatch registry, publishing errors, IAM, Region, and network connectivity |
| Publishing fails with an authorization error | ECS task role, cloudwatch:PutMetricData, and applicable policy conditions |
| Publishing times out | Network path, DNS, VPC endpoint, or egress configuration |
| Metrics appear in another Region | Registry Region versus console Region |
| Some metric series appear, but others do not | Registration, metric filters, dimensions, and publishing errors |
| CloudWatch has data, but the dashboard is blank | Widget configuration, time range, Region, dimensions, and statistic |
Alarm remains in INSUFFICIENT_DATA
|
Evaluation period, missing data, and available datapoints |
Metric is missing from ListMetrics immediately after publishing |
Allow for metric discovery delay and query statistics separately |
13. A repeatable debugging checklist
When custom metrics are missing from CloudWatch, follow this order instead of changing several configurations at once.
- Reproduce the problem: Invoke the operation that should generate the metric.
- Verify application recording: Use Actuator or a test to confirm that the meter exists and changes.
- Verify registry configuration: Confirm that a CloudWatch-compatible registry is configured and active.
- Inspect publishing errors: Check the application logs for AWS API, credentials, or configuration errors.
- Verify the ECS task role: Confirm the identity and permissions used by the application.
- Check network connectivity: Ensure the task can reach the CloudWatch monitoring API.
- Verify Region and namespace: Compare the application configuration with the CloudWatch console.
- Wait for publishing and ingestion: Account for the registry step and CloudWatch visibility delay.
- Query the metric directly: Use the console or AWS CLI to verify that data exists.
- Troubleshoot the dashboard: Check dimensions, time range, period, and statistic.
- Validate the alarm: Confirm the threshold, evaluation periods, missing-data behavior, and notification actions.
This sequence helps isolate the failure at the correct layer.
Conclusion
A Spring Boot application can run successfully while its custom metrics remain unavailable in CloudWatch.
The important thing is to distinguish between recording a metric, publishing it to AWS, querying the resulting data, and displaying it on a dashboard.
Once you understand these stages, troubleshooting becomes a systematic process rather than trial and error.
Start with the application and its active registry. Then verify the publishing configuration, AWS permissions, and network path. Confirm the metric exists in CloudWatch before investigating dashboards and alarms.
That approach is useful not only for Spring Boot applications running on ECS, but also for other Java services that publish custom metrics to CloudWatch.
Official references
- Micrometer registry concepts
- Spring Boot Actuator metrics
- Micrometer CloudWatch integration
- Spring Cloud AWS documentation
- ECS task IAM roles
- CloudWatch ListMetrics API
- CloudWatch GetMetricStatistics API
Have you encountered a situation where your Spring Boot application was healthy but its CloudWatch metrics were missing? What was the root cause? Share your experience in the comments.
Top comments (3)
the task role vs execution role split is the quiet trap here, since fixing permissions on the execution role leaves the app itself still unable to publish.
Absolutely! That's an important distinction. I've covered this in the IAM permissions section under Task Role vs. Task Execution Role. The application needs "cloudwatch:PutMetricData" through its task role, not just the execution role. Thanks for highlighting this common troubleshooting trap!
That PutMetricData detail through the task role clears up why fixing the execution role alone leaves the app silent.