Spring Boot CORS: Cross-Origin Resource Sharing Configuration

Learn how to configure Cross-Origin Resource Sharing in Spring Boot for secure API access, including global config, annotations, and common pitfalls.

published: reading time: 22 min read author: GeekWorkBench
Quick Summary

Learn how to configure Cross-Origin Resource Sharing in Spring Boot for secure API access, including global config, annotations, and common pitfalls. The guide uses practical examples to explain what is cors and why does it matter?, understanding the cors preflight flow and shows how to apply the ideas in a Spring Boot project.

Spring Boot CORS: Cross-Origin Resource Sharing Configuration

Cross-Origin Resource Sharing (CORS) is a browser security mechanism that controls whether web pages can request resources from a different domain. If your Spring Boot API needs to talk to a frontend on a different origin, CORS is what makes or breaks that connection.

Mess it up and your users see nothing but blocked requests and cryptic Access-Control-Allow-Origin errors in the console. This post covers everything you need: concepts, configuration approaches, failure modes, and production hardening.

What is CORS and Why Does It Matter?

Introduction

Cross-Origin Resource Sharing (CORS) controls which browser origins may call a Spring Boot application from a different origin. This guide explains preflight requests, allowed origins and methods, credential handling, and the configuration choices that make cross-origin APIs usable without creating an unnecessary security boundary.

Understanding the CORS Preflight Flow

Before getting into Spring Boot configuration, you need to understand how CORS actually works. The browser sends a preflight request to check permissions before the real request goes through.

sequenceDiagram
    participant Browser as Browser Client
    participant API as Spring Boot API

    Browser->>API: OPTIONS /api/data (Preflight Request)<br/>Origin: http://localhost:3000<br/>Access-Control-Request-Method: GET

    alt Preflight Approved
        API-->>Browser: 200 OK<br/>Access-Control-Allow-Origin: http://localhost:3000<br/>Access-Control-Allow-Methods: GET, POST<br/>Access-Control-Allow-Headers: Content-Type<br/>Access-Control-Max-Age: 3600
        Browser->>API: GET /api/data (Actual Request)<br/>Origin: http://localhost:3000
        API-->>Browser: 200 OK<br/>Access-Control-Allow-Origin: http://localhost:3000<br/>{ "data": "response" }
    else Preflight Denied
        API-->>Browser: 403 Forbidden<br/>No CORS headers
        Note over Browser: Request blocked,<br/>error in console
    end

The preflight is an HTTP OPTIONS request that includes headers describing the actual request the browser wants to send. If the server approves, it responds with allowed methods, headers, and cache duration. The browser then decides whether to proceed with the real request.

Spring Boot CORS Configuration Methods

Spring Boot gives you three main ways to handle CORS. Here’s when to use each.

Method 1: Global CORS Configuration with WebMvcConfigurer

For most applications, this is the way to go. A global configuration applies to all endpoints, so you can audit and change your CORS policy in one place.

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.CorsRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

@Configuration
public class CorsConfig {

    @Bean
    public WebMvcConfigurer corsConfigurer() {
        return new WebMvcConfigurer() {
            @Override
            public void addCorsMappings(CorsRegistry registry) {
                registry.addMapping("/api/**")
                    .allowedOrigins("http://localhost:3000", "https://myfrontend.com")
                    .allowedMethods("GET", "POST", "PUT", "DELETE", "OPTIONS")
                    .allowedHeaders("Content-Type", "Authorization", "X-Request-ID")
                    .exposedHeaders("X-Custom-Header")
                    .allowCredentials(true)
                    .maxAge(3600);
            }
        };
    }
}

exposedHeaders tells the browser which response headers the JavaScript code is allowed to read. One thing to remember: when allowCredentials is true, you cannot use allowedOrigins(“*”) — the browser rejects that combination.

Method 2: Using the @CrossOrigin Annotation

For controller-level or method-level control, @CrossOrigin gives you a declarative approach. This is useful when different endpoints need different CORS policies.

import org.springframework.web.bind.annotation.CrossOrigin;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequestMapping("/api")
public class ProductController {

    // Allow CORS for this specific endpoint
    @CrossOrigin(origins = "http://localhost:3000")
    @GetMapping("/products")
    public List<Product> getProducts() {
        return productService.findAll();
    }

    // Different endpoint, different origin
    @CrossOrigin(origins = "https://admin.example.com", methods = { RequestMethod.GET, RequestMethod.POST })
    @PostMapping("/products")
    public Product createProduct(@RequestBody Product product) {
        return productService.save(product);
    }

    // Allow all origins for this endpoint
    @CrossOrigin(origins = "*", methods = RequestMethod.GET)
    @GetMapping("/public/products")
    public List<Product> getPublicProducts() {
        return productService.findPublic();
    }
}

Put @CrossOrigin at the class level to apply it across all methods, or on individual methods to override. When you combine both, the most permissive settings win.

Method 3: CORS with Spring Security

With Spring Security in the mix, CORS needs to hook into the security filter chain rather than being handled separately.

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.cors.CorsConfigurationSource;
import org.springframework.web.cors.UrlBasedCorsConfigurationSource;

@Configuration
@EnableWebSecurity
public class SecurityConfig {

    @Bean
    public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
        http
            .cors(cors -> cors.configurationSource(corsConfigurationSource()))
            .csrf(csrf -> csrf.disable())
            .authorizeHttpRequests(auth -> auth
                .requestMatchers("/api/public/**").permitAll()
                .requestMatchers("/api/admin/**").hasRole("ADMIN")
                .anyRequest().authenticated()
            );
        return http.build();
    }

    @Bean
    public CorsConfigurationSource corsConfigurationSource() {
        CorsConfiguration configuration = new CorsConfiguration();
        configuration.setAllowedOrigins(Arrays.asList(
            "http://localhost:3000",
            "https://myfrontend.com"
        ));
        configuration.setAllowedMethods(Arrays.asList("GET", "POST", "PUT", "DELETE", "OPTIONS"));
        configuration.setAllowedHeaders(Arrays.asList("Content-Type", "Authorization"));
        configuration.setAllowCredentials(true);
        configuration.setMaxAge(3600L);

        UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
        source.registerCorsConfiguration("/api/**", configuration);
        return source;
    }
}

With Spring Security, the CORS filter runs before authentication kicks in. That is necessary because preflight OPTIONS requests do not carry credentials.

Configuration Properties Reference

Spring Boot also supports CORS through properties, which is handy for environment-specific settings without changing code.

# application.properties

# For actuator endpoints only
management.endpoints.web.cors.allowed-origins=http://localhost:3000,https://monitoring.example.com
management.endpoints.web.cors.allowed-methods=GET,POST
management.endpoints.web.cors.allowed-headers=Content-Type,Authorization
management.endpoints.web.cors.exposed-headers=X-Custom-Header
management.endpoints.web.cors.allow-credentials=true
management.endpoints.web.cors.max-age=3600

These properties only apply to Spring Boot Actuator endpoints. For your application endpoints, you still need one of the programmatic approaches described above.

Failure Scenarios and Common Mistakes

Knowing what can go wrong helps you debug faster.

Misconfiguration: Using “*” with Credentials

A common mistake is trying to use a wildcard with credentials enabled.

// THIS WILL NOT WORK
registry.addMapping("/api/**")
    .allowedOrigins("*")        // Wildcard not allowed with allowCredentials
    .allowCredentials(true);

When allowCredentials is true, browsers require explicit origins. The server must list each one. This combination fails at the browser level, regardless of what headers the server sends.

Misconfiguration: Missing OPTIONS Handler

If preflight requests return 404 or 500, your application is not handling OPTIONS requests. Spring’s CORS support handles this automatically when properly configured, but you need to make sure your filter chain is set up correctly.

Misconfiguration: Case Sensitivity in Headers

CORS headers are case-sensitive. content-type will not match what the browser sends as Content-Type. Always use canonical header names.

Network-Level Blocking

Sometimes the problem is not your application but something in front of it. AWS ALB, nginx, and Cloudflare can all modify or strip CORS headers. If CORS works locally but fails in production, check your infrastructure.

Security Considerations

CORS misconfiguration can expose your API to risks.

Restrict Allowed Origins

Avoid allowedOrigins(“*”) in production unless your API is completely public. Wildcard origins mean any website can make requests to your API on behalf of authenticated users.

Validate Origin Header Carefully

The Origin header comes from the browser and is generally trustworthy because browsers enforce CORS rules. Still, validating origins against an allowlist is good practice.

Do Not Rely on CORS for Authorization

CORS controls which origins can access your resources, not who can access them. Authentication and authorization belong in your application code.

Preflight Caching

A sensible maxAge reduces unnecessary preflight requests. Stable resources benefit from longer cache durations. Resources that change frequently may need shorter ones.

When to Use / When NOT to Use

When CORS Applies

CORS is the right tool when you have a browser-based frontend running on a different origin than your Spring Boot API. Single-page applications, mobile web clients, or any client that runs in a browser and needs to call your API across origins all require CORS headers. Partner integrations that use browser-based JavaScript to call your APIs also need CORS configured.

When to Avoid CORS

CORS is a browser-enforced mechanism. Server-to-server communication, backend services calling each other directly, native mobile applications, and command-line tools do not enforce CORS policies and do not need these headers. If your service mesh or API gateway handles authentication and authorization between services, CORS adds nothing. Do not configure CORS headers for non-browser clients.

When to Use Global Configuration (WebMvcConfigurer)

Use global CORS configuration when you have a consistent CORS policy across most or all of your endpoints. It is easier to audit, modify, and reason about than annotation-based per-endpoint configuration. Changes to the policy happen in one place, which reduces the risk of inconsistent CORS settings across your API.

When to Use @CrossOrigin Annotations

Use @CrossOrigin on specific controllers or methods when different endpoints genuinely need different CORS policies. If your API exposes both a public set of endpoints and an admin-only set that should allow different origins, annotation-based configuration makes that boundary explicit in the code.

When to Use Spring Security CORS Integration

If you are already using Spring Security, integrate CORS into your security filter chain rather than using a separate configuration. This ensures CORS processing happens before authentication, which is required since preflight OPTIONS requests do not carry credentials.

Common Pitfalls / Anti-Patterns

Using Wildcard Origins with Credentials

Setting allowedOrigins(“*”) while also enabling allowCredentials(true) is rejected by browsers. This combination would allow any website to make authenticated requests on behalf of a logged-in user, which is a security risk. When credentials are required, you must enumerate explicit origins.

Fix: List each allowed origin explicitly. If you have many origins, consider an origin validator that checks against a configured allowlist rather than hardcoding every domain.

Missing OPTIONS Handler for Preflight

If preflight OPTIONS requests return 404 or 500 instead of proper CORS headers, the browser blocks the subsequent actual request. Spring’s CORS filter handles this automatically when the filter chain is correctly configured, but a missing or misconfigured filter chain can silence it.

Fix: Verify your CORS configuration is registered as a filter and that it runs before any authentication filters. Test preflight with curl -X OPTIONS -H “Origin: http://localhost:3000” -H “Access-Control-Request-Method: GET” http://localhost:8080/api/endpoint and confirm you get Access-Control-Allow-* headers back.

Case-Sensitive Header Names

CORS headers are case-sensitive. Browsers send Content-Type and Authorization with canonical capitalization. If your configuration uses lowercase content-type, the preflight check fails silently at the browser level.

Fix: Always use canonical header names in CORS configuration. Spring’s HttpHeaders class uses canonical names internally, so stick to the constants like HttpHeaders.CONTENT_TYPE.

Infrastructure Stripping CORS Headers

When your Spring Boot app sits behind an AWS Application Load Balancer, nginx, Cloudflare, or a reverse proxy, those intermediate layers can modify or strip CORS headers. CORS works locally but fails in staging or production for this reason.

Fix: Check the response headers at each hop in your infrastructure chain. Ensure your load balancer and proxy configurations pass through Access-Control-* headers. This is a infrastructure problem, not a Spring Boot problem.

Not Caching Preflight Results

Without a maxAge setting, the browser sends a preflight OPTIONS request before every actual request. For high-frequency API calls, this doubles your request volume. Preflight caching eliminates this overhead for stable CORS policies.

Fix: Set maxAge(3600) (or a sensible duration for your policy stability) on your CORS configuration to cache preflight results for an hour.

Using CORS for Authorization

CORS only controls which origins can access your resources. It does not authenticate users or authorize actions. A request that passes CORS checks can still be rejected by your business logic for authorization reasons. Do not treat CORS headers as a security control.

Fix: Implement proper authentication and authorization in your application code, independent of CORS configuration.

Trade-off Table

Scenario Recommended Configuration Rationale
Development environment allowedOrigins(“*”) Simplifies testing across multiple localhost ports
Single frontend domain Specific origin list Restricts access to known domains only
Multiple trusted domains Origin patterns with ** Supports subdomains without listing each
Credentials required Explicit origins, allowCredentials(true) Required combination for auth cookies/tokens
Public API allowedOrigins(“*”) with short maxAge No credential-based access expected
Third-party integration Dedicated CORS config per integration Isolates third-party access policies

Observability Checklist

Having the right logging and monitoring in place makes CORS debugging tractable.

Verify that your application logs include CORS-related information at DEBUG or TRACE level. Spring’s CorsProcessor logs show when requests are allowed or denied and why.

Track the ratio of OPTIONS requests to actual requests. A spike in OPTIONS requests often means clients are not caching preflight results, which hurts performance.

Set up alerts for missing Access-Control-Allow-Origin headers in responses where you expect them.

Test CORS behavior in each environment using browser DevTools. Chrome’s Network tab shows CORS headers in responses, and the Console displays clear error messages when requests are blocked.

Quick Recap Checklist

Use this checklist when implementing or auditing CORS configuration.

  • Identify all origins that need API access
  • Choose global configuration, annotations, or Spring Security integration
  • Never use allowedOrigins(“*”) with allowCredentials(true)
  • Handle OPTIONS requests for all CORS-enabled endpoints
  • Set appropriate maxAge for preflight caching
  • Validate CORS headers in development browser tools
  • Test in staging environment with same-origin restrictions as production
  • Document allowed origins for future maintainers
  • Review CORS configuration during security audits
  • Monitor CORS error rates in production

Interview Questions

1. What is the purpose of a CORS preflight request?

A preflight request is an HTTP OPTIONS request the browser sends before the actual cross-origin request. It asks the server what origins, methods, and headers are permitted for the request that follows. This gives servers a chance to validate whether the request should be allowed before the potentially expensive or state-changing actual request is sent. Preflight results can be cached using the Access-Control-Max-Age header so the browser does not repeat the check for every request.

2. Why can you not use allowedOrigins("*") with allowCredentials(true)?

It would be a security hole. If credentials are allowed and any origin is allowed, any website could make authenticated requests to your API if a user visits a malicious page while already logged in. The CORS spec requires explicit origins when credentials are involved. Browsers enforce this rejection at the client level.

3. How does CORS work with Spring Security compared to standard Spring MVC?

Spring Security integrates CORS into its filter chain rather than handling it separately. You configure a CorsConfigurationSource bean and pass it to the cors() method in your SecurityFilterChain. This ensures CORS headers are processed before authentication logic runs, which is necessary because preflight OPTIONS requests do not carry credentials. Spring Security also covers WebSocket endpoints and other protocol upgrades that standard MVC filters might miss.

4. What is the difference between allowedHeaders and exposedHeaders?

allowedHeaders controls which request headers the browser may send — things like Content-Type, Authorization, or custom headers your JavaScript code sets. exposedHeaders controls which response headers the browser makes accessible to JavaScript code. By default, only a small set of safe response headers are exposed. If your API returns custom headers that the frontend needs to read, you must list them in exposedHeaders.

5. How do you debug CORS issues in production when you cannot use browser developer tools?

You can simulate preflight checks with curl. Send an OPTIONS request with the appropriate Origin and Access-Control-Request-Method headers and see what your server actually returns. Compare those response headers against what the browser expects. Check any proxies, load balancers, or gateways between your app and the internet. Enable detailed CORS logging in your application and look at access logs for OPTIONS request patterns and response codes. For ongoing monitoring, instrument your frontend to report CORS errors to your logging service.

6. How does the sameSite cookie attribute interact with CORS credentials?

The sameSite attribute controls when cookies are sent with cross-site requests. With sameSite=strict, cookies are never sent on cross-origin requests. With sameSite=lax, cookies are sent only for safe methods (GET, HEAD, OPTIONS) but not for POST, PUT, DELETE. With sameSite=none and Secure=true, cookies are sent on all cross-origin requests that also pass CORS validation. When using CORS with credentials, ensure your cookie's sameSite setting is compatible with your CORS origin policy, or users will experience authentication failures even when CORS headers are correct.

7. What is the difference between the Origin header and the Referer header in CORS context?

The Origin header is sent automatically by the browser during cross-origin requests and indicates the exact origin (protocol + domain + port) of the requesting page. It cannot be modified by JavaScript, making it trustworthy for CORS validation. The Referer header is older, contains the full URL of the requesting page (including path), can be stripped by browsers or extensions, and is not sent in all cross-origin scenarios. CORS specifications require Origin validation, not Referer validation, because Origin is more reliable and consistent.

8. Why does CORS affect API design decisions around authentication?

CORS forces a separation between authentication methods. Cookie-based authentication requires careful sameSite and CORS credential configuration, which can be complex with multiple trusted origins. Token-based authentication (JWT in headers) works more naturally with CORS because the Authorization header is explicitly allowed and does not involve cookie state. Many modern API designs prefer header-based tokens specifically to avoid CORS credential complications. Additionally, CORS means authentication logic must not rely on cookies for cross-origin scenarios, pushing toward stateless token designs.

9. What are the performance implications of CORS on API requests?

Without preflight caching (maxAge), every cross-origin request triggers a preflight OPTIONS request before the actual request, effectively doubling network round trips for non-simple requests. For high-frequency APIs, this significantly impacts latency and throughput. Setting appropriate maxAge values (e.g., 3600 seconds) caches preflight results, eliminating preflight overhead for subsequent requests within the cache window. However, longer maxAge means changes to CORS policy take longer to propagate since browsers cache the old configuration.

10. How does CORS interact with API gateways and reverse proxies?

API gateways and reverse proxies (nginx, AWS ALB, Cloudflare) sit between the browser and your Spring Boot application and can modify or strip CORS headers. If CORS headers set by your application are removed by the proxy, browsers see no CORS approval and block requests. You must configure your proxy to pass through Access-Control-* headers, or configure CORS directly at the proxy layer. Many teams configure CORS at the gateway to handle it centrally rather than in each microservice, which is a valid architectural choice but requires consistent policy management.

11. What is the relationship between CORS and the Same-Origin Policy?

The Same-Origin Policy (SOP) is the broad restriction that prevents scripts from one origin accessing resources from another. CORS is a specific mechanism that relaxes SOP by allowing servers to explicitly declare which cross-origin requests they permit. Without CORS, the browser blocks all cross-origin requests regardless of server intent. CORS adds the Access-Control-Allow-Origin header as a server-controlled override. SOP blocks both requests and responses; CORS allows servers to unblock responses by sending the appropriate header.

12. How should CORS be configured in a microservices architecture?

In microservices, you have two main approaches. First, configure CORS at the API gateway or load balancer layer so all services share one CORS policy. Second, configure CORS per-service when services need different policies (e.g., internal services vs public-facing ones). Service-to-service communication without browsers does not need CORS. If using Spring Cloud Gateway or a similar edge proxy, CORS is typically configured there. Ensure that whatever layer handles CORS runs before authentication filters since preflight requests lack credentials.

13. What security headers complement CORS protection?

CORS works alongside other security headers. Content-Security-Policy (CSP) specifies which resources can be loaded and can restrict form submissions and script sources. Strict-Transport-Security (HSTS) ensures connections use HTTPS, preventing man-in-the-middle attacks that could modify Origin headers. X-Content-Type-Options prevents MIME type sniffing. The Referrer-Policy controls how much referrer information is shared. These headers together create defense in depth — CORS controls origin access while others control resource loading and transport security.

14. Can CORS be used to prevent CSRF attacks?

CORS does not prevent CSRF attacks. CORS controls which origins can make requests, but a malicious site can still send requests (without CORS headers) from a user's browser to your site — the browser will send cookies automatically. The SameSite cookie attribute is actually the effective defense against CSRF. CORS can help in a limited way if you require Authorization headers (which cannot be sent cross-origin without CORS permission), but the standard CSRF mitigation is SameSite cookies, CSRF tokens, or double-submit patterns. Do not rely on CORS for CSRF protection.

15. What happens to CORS when a redirect occurs on a cross-origin request?

When a cross-origin request receives a redirect, the browser does not automatically include CORS headers on the redirected request. If your API returns a 302 redirect to a different origin, the browser will not follow it with CORS credentials. The preflight applies only to the initial URL, not to redirects. For API designs that use redirects, ensure redirects stay within allowed origins, or use JavaScript-based redirect handling that explicitly makes a new request to the redirect target with proper CORS context.

16. How does CORS handle non-standard HTTP methods like PATCH or TRACE?

Non-standard methods must be explicitly listed in allowedMethods. By default, browsers only "simple" methods (GET, POST, HEAD) without preflight. Methods like PATCH, PUT, DELETE, or TRACE trigger preflight because they are less common. If your API uses PATCH for partial updates, you must include "PATCH" in allowedMethods. Similarly, custom HTTP methods your API defines must be explicitly allowed. This is a common oversight that causes PATCH requests to fail with CORS errors even when GET and POST work fine.

17. What is the difference between a simple request and a preflighted request in CORS?

Simple requests meet all these criteria: GET, POST, or HEAD method; only safe headers (Accept, Accept-Language, Content-Language, Content-Type with application/x-www-form-urlencoded, multipart/form-data, or text/plain); no custom headers; no ReadableStream or XMLHttpRequestUpload objects. Simple requests go directly to the server with the Origin header and expect CORS headers in the response. Preflighted requests fail any of these criteria, trigger an OPTIONS preflight first, and wait for approval before sending the actual request. Understanding this distinction helps you optimize by avoiding unnecessary preflights.

18. How does Content-Type affect whether a request is preflighted?

Content-Type determines if a POST request is simple or requires preflight. POST with application/x-www-form-urlencoded, multipart/form-data, or text/plain is simple and skips preflight. POST with application/json (Content-Type: application/json) is not simple because it indicates structured data that might include custom headers, triggering a preflight. This is why sending JSON to your API from a frontend almost always requires CORS preflight handling. If you use form-encoded data for your API, you can avoid preflight complexity but lose the ability to send complex nested structures easily.

19. What are common browser CORS error messages and their meanings?

Access-Control-Allow-Origin missing means the server did not send any CORS header — configure CORS or check if the request is reaching your app. Access-Control-Allow-Origin value not matching Origin header means your allowedOrigins list does not include the requesting origin. Response to preflight has invalid HTTP status code means the OPTIONS endpoint returned something other than 200/204 — check your filter chain. Credentials not supported if Origin header is "*" is the wildcard+credentials rejection. Access-Control-Allow-Methods does not match indicates your allowedMethods does not include the method the browser wants to use.

20. When would you choose a CORS proxy instead of configuring CORS headers?

Use a CORS proxy when you cannot configure the target server (third-party APIs, legacy systems) or when you need to aggregate multiple APIs under one origin for frontend convenience. The proxy adds CORS headers on behalf of the target. However, proxies become a bottleneck, add latency, hide errors, and introduce a single point of failure. If you control the server, configure CORS directly — it is more efficient, more reliable, and gives you full control over the policy. Proxies are a last resort for third-party APIs, not a preferred architecture.

Further Reading

Conclusion

CORS is a browser-enforced mechanism that governs whether a web page at one origin can make requests to a server at a different origin. It exists because browsers enforce the same-origin policy by default, and CORS is the controlled relaxation of that policy through explicit server-side headers. Understanding that CORS does not apply to server-to-server communication—and therefore does not matter for backend services calling each other, native mobile apps, or CLI tools—is the foundation for not over-engineering CORS solutions where they are not needed.

Spring Boot gives you three ways to configure CORS: global configuration via WebMvcConfigurer, per-controller or per-method @CrossOrigin annotations, and Spring Security integration. Global configuration via WebMvcConfigurer is the right default for most applications because it keeps CORS policy in one auditable place. @CrossOrigin is useful when different endpoints genuinely need different origin policies. The Spring Security integration is required when you are already using Spring Security, because CORS filters must run before authentication filters for preflight OPTIONS requests to work correctly.

The most common CORS mistake in production is allowing allowedOrigins(“*”) while also setting allowCredentials(true)—a combination browsers explicitly reject as a security risk. Another common failure is forgetting that preflight OPTIONS requests must be handled, which happens automatically in Spring MVC but only if your filter chain is correctly configured. When your application sits behind an AWS ALB, nginx, or Cloudflare, those intermediate layers can strip CORS headers, causing failures that are impossible to reproduce locally. Always test CORS end-to-end in an environment that mirrors your production infrastructure topology. Setting a sensible maxAge value avoids unnecessary preflight round trips on every request for stable CORS policies, reducing latency for high-frequency API calls.

Category

Related Posts

Spring Boot Build Tools: Maven & Gradle

Configure Maven and Gradle for Spring Boot projects—plugins, dependency management, packaging JARs and WARs, and build automation essentials.

#spring-boot #spring-boot-roadmap #learning-path

Embedded Web Servers in Spring Boot: Tomcat, Jetty, Undertow

Configure embedded servers in Spring Boot: compare Tomcat, Jetty, and Undertow, tune thread pools, enable access logs, and switch implementations.

#spring-boot #spring-boot-roadmap #learning-path

JUnit 5 & Jupiter: Lifecycle, Nested & Parameterized Tests

Explore JUnit 5 Jupiter features: master test lifecycle annotations, organize tests with @Nested, and parameterize tests with @CsvSource and @MethodSource.

#spring-boot #spring-boot-roadmap #learning-path