Spring Boot Actuator: Only One Endpoint Is On By Default
Add the actuator starter and exactly one endpoint is exposed — health. Everything else, including metrics, env, beans and loggers, returns 404 until you list it in management.endpoints.web.exposure.include. The one to learn first is loggers: a POST changes a log level on a running process, verified here to start SQL logging with no restart. And all of it is unauthenticated by default.
Add the actuator dependency, start the application, and open /actuator. Here is the whole response:
{"_links":{
"self": {"href":"http://localhost:8080/actuator"},
"health": {"href":"http://localhost:8080/actuator/health"},
"health-path": {"href":"http://localhost:8080/actuator/health/{*path}"}
}}
One endpoint, listed twice. If you arrived here because a tutorial promised /actuator/metrics and /actuator/beans and you got a 404, nothing is broken — you are seeing the default, and most writing about Actuator describes a project that had already changed it.
Everything below was measured on a Spring Boot 4.1.1 application with web, actuator and data-jpa.
What you get by default: one endpoint
The four endpoints people expect most, on a fresh application:
/actuator/metrics -> 404
/actuator/env -> 404
/actuator/beans -> 404
/actuator/loggers -> 404
They exist. They are not exposed. This is the right default — env prints your configuration and loggers can change behaviour — and it is the reason so many Actuator articles do not reproduce.
Turning the rest on
One property, and you name what you want rather than asking for everything:
management.endpoints.web.exposure.include=health,info,metrics,loggers,env
/actuator then lists ten links:
env, env-toMatch, health, health-path, info,
loggers, loggers-name, metrics, metrics-requiredMetricName, self
The -name and -required... variants are the templated forms — /actuator/loggers/{name} and /actuator/metrics/{name} — which is how you address one logger or one metric rather than the index.
Warning
include=*works and is worth not getting into the habit of. Listing endpoints by name means adding one is a decision; a wildcard means the next Spring Boot release can expose something new on your public interface without anyone choosing it.
Health, and the two groups you did not configure
With no configuration at all:
{"groups":["liveness","readiness"],"status":"UP"}
Two health groups are already there. Add management.endpoint.health.show-details=always and the components appear:
db, diskSpace, livenessState, ping, readinessState, ssl
db is in that list because a DataSource exists on the classpath. Health indicators are discovered, not declared — add a Redis or a message broker and their checks join the list without you writing anything, which is convenient and also means your health endpoint quietly starts depending on more things.
Why liveness and readiness are different questions
They get conflated constantly, and the consequences of swapping them are opposite:
- Liveness — is this process broken beyond recovery? A failure means restart it.
- Readiness — can it serve traffic right now? A failure means stop routing to it, and leave it running.
An application waiting on a slow dependency at startup is not ready and perfectly alive. Wire that to a liveness probe and the orchestrator kills it, repeatedly, while it tries to start. Those endpoints are /actuator/health/liveness and /actuator/health/readiness, and they are what container probes should point at — the distinction matters as soon as something other than you decides when to restart your process, which is the difference a scheduler makes.
info returns {} and that is not a bug
info.app.name=shop
{}
The property is set and the endpoint is empty. The contributor that reads info.* from your configuration is disabled by default; turn it on and the same application answers:
management.info.env.enabled=true
{"app":{"name":"shop"}}
Both of those are from real runs of the same jar. This one costs people twenty minutes reliably, because the property that is missing is not the one they set.
Metrics, and what the endpoint is for
/actuator/metrics on this small application lists 68 metric names — application.ready.time, disk.free, and so on. Drilling into one:
curl localhost:8080/actuator/metrics/jvm.memory.used
name: jvm.memory.used | baseUnit: bytes | value: 137 MB
availableTags: [area, id]
The tags are how you narrow it — heap versus non-heap, or one memory pool. Underneath this is Micrometer, and the important thing to understand is what this endpoint is not: it is a debugging view, one metric at a time, for a human with curl. A monitoring system scrapes a different endpoint in its own format. Do not build a dashboard on top of this one.
The loggers endpoint, which is the reason to have Actuator
This is the feature worth the dependency. Ask about a logger:
curl localhost:8080/actuator/loggers/org.hibernate.SQL
configuredLevel = null effectiveLevel = INFO
configuredLevel is null because nobody set it; effectiveLevel is what it inherits. Now change it on the running process:
curl -X POST localhost:8080/actuator/loggers/org.hibernate.SQL \
-H "Content-Type: application/json" \
-d '{"configuredLevel":"DEBUG"}'
HTTP 204
configuredLevel = DEBUG effectiveLevel = DEBUG
And it is not just bookkeeping. The very next request to a database-backed endpoint produced this in the log, with no restart:
DEBUG org.hibernate.SQL : select count(p1_0.id) from product p1_0
Think about what that replaces. The old version of this is: notice a problem, add a logging property, build, deploy, wait for the rollout, hope the problem recurs. The new version is one POST, and one more to set it back to INFO when you are done. In the middle of an incident that difference is the whole game.
Tip
Use it and then undo it. A
DEBUGlevel left on a chatty logger produces log volume that costs money and buries the next investigation. POST{"configuredLevel":null}to reset a logger to inherited.
All of this is unauthenticated
Everything above was done with plain curl and no credentials, against an application that has no security configuration. That is fine on a laptop and is a real problem in production:
envpublishes your configuration, including property names you would rather not advertisebeanspublishes your wiring, which is a map of the applicationloggerschanges behaviour on a running process, over HTTP, with no authentication
Two answers, and they compose:
## 1. a port you do not route publicly
management.server.port=9001
Or secure the paths, which is the same job as securing anything else — if you are already issuing tokens, the filter chain you wrote for that is where /actuator/** belongs, with the health endpoints usually left open so probes keep working.
The rule that survives both: expose by name, not by wildcard, and know which of the endpoints you exposed can change something rather than only report it.
If you are starting from scratch, creating the project with the actuator dependency ticked gets you the one-endpoint default above, and the properties in this article are the whole distance from there to something useful.
Frequently asked questions
- Why does /actuator only show health?
- Because that is the default and it is deliberate. Spring Boot exposes only the health endpoint over HTTP; metrics, env, beans and loggers all exist but return 404 until you name them in management.endpoints.web.exposure.include. Most tutorials were written against a project that had already changed this.
- Why does /actuator/info return an empty object?
- Because the contributor that reads info.* properties from your configuration is disabled by default. Setting management.info.env.enabled=true turns it on — verified, the same application then returned {"app":{"name":"shop"}} where it had returned {}.
- Can I change a log level without redeploying?
- Yes, and it is the best reason to have Actuator. POST {"configuredLevel":"DEBUG"} to /actuator/loggers/your.package.name, get a 204, and the change is live. Measured here: SQL logging started producing DEBUG lines on the next request, with no restart.
- What is the difference between liveness and readiness?
- Liveness answers "is this process broken beyond recovery" — a failure means restart it. Readiness answers "can it serve traffic right now" — a failure means stop sending requests but leave it alone. Both groups exist in /actuator/health out of the box.
- Is Actuator safe to expose?
- Not as-is. env and beans publish your configuration and your wiring, and loggers changes behaviour on a running process. Either bind Actuator to a separate management port that is not publicly routed, or secure the /actuator paths — and expose endpoints by name rather than with a wildcard.