Refactor CORS handling into injectable components - #12982
Conversation
Extract origin configuration lookup, response decoration, and preflight validation from CorsFilter into dedicated components. Add a configuration option to disable the CORS filter entirely, preserve deprecated extension points for compatibility, and expand coverage for CORS utilities, preflight validation, and filter configuration.
There was a problem hiding this comment.
🟡 Changes recommended
There are correctness issues (including a route-builder call in a new test that should not compile) and several newly introduced @since tags that don’t match the branch version.
Once you've addressed the issues Copilot identified, you can request another Copilot review.
Pull request overview
This pull request refactors Micronaut HTTP server CORS handling by extracting CorsFilter responsibilities into injectable strategy components, while adding configuration to disable the filter entirely and expanding test coverage around the new CORS utilities and preflight validation.
Changes:
- Introduces injectable strategy APIs and default implementations for origin configuration retrieval, preflight validation, and response decoration.
- Adds
micronaut.server.cors.filter.enabledconfiguration (default enabled) to disable the CORS filter bean entirely. - Adds/expands tests for
CrossOriginUtil, preflight validation behavior, and filter enablement configuration.
File summaries
| File | Description |
|---|---|
| http-server/src/test/java/io/micronaut/http/server/cors/PreflightRequestValidatorTest.java | New unit test coverage for preflight validator behavior against routes/headers/private-network. |
| http-server/src/test/java/io/micronaut/http/server/cors/CrossOriginUtilTest.java | New unit tests for CrossOriginUtil matching and annotation-to-config conversion. |
| http-server/src/test/groovy/io/micronaut/http/server/HttpServerConfigurationSpec.groovy | Adds assertions for default-enabled and configurable disabling of the CORS filter bean. |
| http-server/src/main/java/io/micronaut/http/server/HttpServerConfiguration.java | Adds nested cors.filter configuration model to control filter enablement. |
| http-server/src/main/java/io/micronaut/http/server/cors/PreflightRequestValidator.java | New strategy interface for validating preflight requests. |
| http-server/src/main/java/io/micronaut/http/server/cors/DefaultPreflightRequestValidator.java | Default implementation performing method/header/private-network checks and route/static-resource validation. |
| http-server/src/main/java/io/micronaut/http/server/cors/DefaultCorsResponseDecorator.java | Default implementation responsible for writing CORS response headers. |
| http-server/src/main/java/io/micronaut/http/server/cors/DefaultCorsOriginConfigurationRetriever.java | Default implementation resolving route-level CORS config first, then server-level config. |
| http-server/src/main/java/io/micronaut/http/server/cors/CrossOriginUtil.java | Expands shared CORS utility/matching/conversion logic for reuse across the new components. |
| http-server/src/main/java/io/micronaut/http/server/cors/CorsResponseDecorator.java | New strategy interface for CORS response header decoration. |
| http-server/src/main/java/io/micronaut/http/server/cors/CorsOriginConfigurationRetriever.java | New strategy interface for resolving the applicable origin configuration from origin + route matches. |
| http-server/src/main/java/io/micronaut/http/server/cors/CorsOriginConfiguration.java | Javadoc enhancements for existing CORS origin configuration accessors. |
| http-server/src/main/java/io/micronaut/http/server/cors/CorsFilter.java | Refactors filter to delegate to injected strategy components and adds @Requires gating via configuration. |
Review details
Suppressed comments (3)
http-server/src/main/java/io/micronaut/http/server/HttpServerConfiguration.java:927
setFilteris marked@since 5.4.0, but this branch’sprojectVersionis5.2.0-SNAPSHOTand the setter is introduced here; please align the@sincetag with the branch debut version.
* @since 5.4.0
http-server/src/main/java/io/micronaut/http/server/HttpServerConfiguration.java:1050
- The new
CorsFilterConfigurationnested type is marked@since 5.4.0, but this branch is5.2.0-SNAPSHOTand the type is introduced in this PR. Update the@sincetag to match the actual debut version.
* @since 5.4.0
http-server/src/main/java/io/micronaut/http/server/cors/CrossOriginUtil.java:224
getCorsOriginConfigurationForAnnotationMetadataProvideris introduced here but is marked@since 5.4.0. On this5.2.0-SNAPSHOTbranch, the@sincetag should be5.2.0.
* @since 5.4.0
- Files reviewed: 13/13 changed files
- Comments generated: 4
- Review effort level: Lite
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
| } | ||
|
|
||
| void addRoute(TestController controller) { | ||
| GET(ROUTE, controller); |
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
Replace this usage of 'Stream.collect(Collectors.toList())' with 'Stream.toList()' and ensure that the list is unmodified.
Throw UnsupportedOperationException from the deprecated CorsFilter header mutation methods because CORS response headers are now populated by CorsResponseDecorator. > Add a nested comment explaining why this method is empty, throw an UnsupportedOperationException or complete the implementation.
Set Access-Control-Request-Private-Network to "true" instead of the requested HTTP method when building CORS preflight requests.
dstepanov
left a comment
There was a problem hiding this comment.
Reviewed the extraction against the original CorsFilter logic method by method — DefaultCorsOriginConfigurationRetriever, DefaultPreflightRequestValidator and DefaultCorsResponseDecorator reproduce the previous behaviour faithfully, including the route-level → server-wide fall-through and the !corsConfiguration.isEnabled() short-circuit. The two deliberate behaviour changes both look like fixes: (AnnotationMetadata) rm → rm.getAnnotationMetadata(), and gating Access-Control-Allow-Private-Network on .filter(Boolean.TRUE::equals) rather than header presence — which is what makes the TCK change meaningful, since that assertion was previously vacuous.
Three things worth a look, inline. Two non-blocking notes as well:
- The new public SPIs (
CorsResponseDecorator,PreflightRequestValidator,CorsOriginConfigurationRetriever) point@DefaultImplementationat package-private@Internalclasses, so a consumer can replace them but cannot subclass or delegate to the defaults — theprotectedmethods onDefaultCorsResponseDecoratorare unreachable from outside the package. micronaut.server.cors.filter.enabledgets no entry insrc/main/docs/guide/httpServer/serverConfiguration/cors.adoc, which is where the distinction fromcors.enabledmost needs explaining.
|
I agree this is a breaking change and can't go into a minor release |
Route default CORS response decoration through the deprecated protected mutators so existing CorsFilter subclasses can continue customizing headers, while preserving custom CorsResponseDecorator behavior. Delegate the default mutators to DefaultCorsResponseDecorator, handle filters without a router, and add coverage for standard and short-circuited preflight responses.
|
@graemerocher the PR should not longer be a breaking change. |
| * | ||
| * @since 5.2.0 | ||
| */ | ||
| @ConfigurationProperties("filter") |
There was a problem hiding this comment.
why is the namespace here just "filter" and not "cors-filter"?
| return null; | ||
| } | ||
| for (RouteMatch<?> routeMatch : routeMatches) { | ||
| Optional<CorsOriginConfiguration> originConfigurationOptional = |
There was a problem hiding this comment.
optional adds overhead and this looks like some kind of internal API call so seems unnecessary
| * @author Sergio del Amo | ||
| * @since 3.9.0 | ||
| */ | ||
| public final class CrossOriginUtil { |
There was a problem hiding this comment.
review this class and see what needs to be internal / non-public
adding lots of what seem to be internal focused methods to the public API surface doesn't seem wise
|



Extract origin configuration lookup, response decoration, and preflight validation from CorsFilter into dedicated components.
Add a configuration option to disable the CORS filter entirely, preserve deprecated extension points for compatibility, and expand coverage for CORS utilities, preflight validation, and filter configuration.
Related to: micronaut-projects/micronaut-security#2271