Stop Swagger UI from Exposing Internal Endpoints
Prevent internal Spring Boot endpoints from appearing in Swagger UI and gate the interactive docs by profile using springdoc and @Hidden.
29 Jan 2026, 20:02 UTC

Internal endpoints should not appear in your public API docs
Swagger UI is useful for testing and sharing REST contracts, but it can also become an accidental inventory of internal endpoints. A controller method meant for internal health checks, admin tasks, or legacy callbacks often ends up in the generated OpenAPI specification just because it is annotated with @RestController. If that UI is reachable outside your network, attackers get a map of paths they were never meant to see.
The practical takeaway is twofold: mark internal operations so Springdoc omits them from the OpenAPI spec, and disable the Swagger UI itself in production profiles. This keeps the documentation contract honest without removing the endpoint from the running application.
Terms used here
- OpenAPI specification: a machine-readable description of your API, usually served as JSON at
/v3/api-docs. - Swagger UI: the browser interface that reads that JSON and lets users call endpoints directly.
- Springdoc OpenAPI: the Spring Boot library that scans controllers and generates the specification automatically.
Hide an endpoint with @Hidden
Springdoc supports @Hidden from io.swagger.v3.oas.annotations. When placed on a controller method, the operation is excluded from the generated OpenAPI document and therefore from Swagger UI. The endpoint still works; it simply stops appearing in the docs.
package com.example.api;
import io.swagger.v3.oas.annotations.Hidden;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
@RestController
@RequestMapping("/api/v1")
public class OrdersController {
@GetMapping("/orders/{id}")
public ResponseEntity getOrder(@PathVariable Long id) {
return ResponseEntity.ok(new OrderDto(id));
}
@Hidden
@PostMapping("/internal/reindex")
public ResponseEntity reindex() {
// Internal maintenance task, not part of the public contract
return ResponseEntity.noContent().build();
}
}After adding this annotation, restart the application and inspect /v3/api-docs. The /api/v1/internal/reindex path should no longer appear under paths, while /api/v1/orders/{id} remains.
Gate Swagger UI by Spring profile
Removing an endpoint from the spec does not protect the endpoint itself. It also does not stop someone from opening Swagger UI in production. A safer default is to enable the UI only in non-production profiles.
In src/main/resources/application.yml, disable it globally:
springdoc:
swagger-ui:
enabled: falseThen enable it only in a development profile, for example application-dev.yml:
springdoc:
swagger-ui:
enabled: trueStart the app with the active profile:
# Run from the project root. Replace ./mvnw with mvn if using a system Maven install.
./mvnw spring-boot:run -Dspring-boot.run.profiles=devExpected checks:
- With the
devprofile,http://localhost:8080/swagger-ui.htmlshould load the UI. - With the
prodprofile (or no profile when the default is disabled), that URL should return 404 or be blocked by your server configuration.
Required permissions: read/write access to the repository and permission to run the build. Risk: disabling the UI does not authenticate the API; internal paths remain reachable over HTTP if network controls allow it.
Limitation: documentation is not access control
Using @Hidden and profile-based toggles changes what developers see, not who can call an endpoint. A hidden path still responds to requests unless you also add Spring Security, firewall rules, or controller-level authorization. Treat these steps as hygiene for documentation, not a replacement for authentication.
Verification checklist
- Add the
springdoc-openapi-starter-webmvc-uidependency to your build file if it is not already present. - Annotate internal controller methods with
@Hidden. - Set
springdoc.swagger-ui.enabled=falsein the base configuration andtrueonly in your development profile. - Start the application with the dev profile and open
/v3/api-docs; confirm hidden paths are absent. - Start with the production profile and confirm
/swagger-ui.htmlis not publicly reachable.
If the spec still contains a hidden path, check that the annotation is imported from io.swagger.v3.oas.annotations.Hidden and not a similarly named library. If the UI loads in production, verify the active profile and that no security filter chain is overriding the disabled setting.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.