-
-
Notifications
You must be signed in to change notification settings - Fork 589
Scalar source URLs resolve to "null/<groupName>" on second request when using GroupedOpenApi #3230
Description
Describe the bug
When using GroupedOpenApi beans (or springdoc.group-configs), the Scalar UI works on the first page load but breaks on every subsequent load (including hard refresh). The rendered HTML contains null/groupName as source URLs instead of the correct /v3/api-docs/groupName.
For example, the generated Scalar config contains:
"sources":[
{"url":"null/default","title":"default","default":false},
{"url":"null/service-to-service","title":"service-to-service","default":false}
]
Root cause from analysis:
AbstractScalarController.getDocs() builds the sources array from group configs, then calls scalarProperties.setUrl(null). Although SpringBootScalarProperties is @scope("prototype"), the controller is a singleton that holds a single reference to the properties instance. The setUrl(null) call mutates that instance, so on the next request buildApiDocsUrl() reads getUrl() which returns null, and the source URLs are built as null + "/" + groupName.
Relevant flow in AbstractScalarController.getDocs():
- configureProperties() calls buildApiDocsUrl() which returns scalarProperties.getUrl() → correct on first call
- Sources are built: url + "/" + group → correct
- scalarProperties.setUrl(null) → corrupts the instance
- Next request: scalarProperties.getUrl() → null → sources become "null/default"
To Reproduce
- Spring Boot 3.4.x
- springdoc-openapi-starter-webmvc-scalar 2.8.15 (pulls in scalar-core 0.5.8, scalar-webmvc 0.5.8)
Minimal config:
(application.yml)
springdoc:
cache:
disabled: true
swagger-ui:
enabled: false
api-docs:
path: /v3/api-docs
scalar:
url: /v3/api-docs
path: /scalar
@Bean
fun defaultOpenApi(): GroupedOpenApi =
GroupedOpenApi.builder()
.group("default")
.pathsToMatch("/**")
.build()
@Bean
fun serviceApi(): GroupedOpenApi =
GroupedOpenApi.builder()
.group("service-to-service")
.packagesToScan("com.example.s2s")
.build()
scalar:
path: /scalar
Steps:
- Start the application
- Open /scalar in the browser → works correctly
- Refresh the page → source URLs are null/default, null/service-to-service
Workaround
We resolved the issue by removing the GroupedOpenApi beans entirely, so Scalar uses scalar.url directly without entering the sources/group code path. This works but means we lose the ability to split our API docs into separate groups (e.g. public API vs service-to-service endpoints). We'd like to keep using groups with Scalar.
Expected behavior
Source URLs should be correct on every request, e.g. /v3/api-docs/default and /v3/api-docs/service-to-service.
Screenshots
first load:
second load (only pressed refresh):
