Skip to content
Spring Boot sb web 3 min read

CORS Configuration

CORS (Cross-Origin Resource Sharing) is a browser security mechanism that controls whether a web page from one origin may call an API on a different origin. When your React/Angular front end on http://localhost:3000 calls a Spring Boot API on http://localhost:8080, the browser enforces CORS. This page shows per-handler, controller, and global configuration, plus the Spring Security interaction.

What CORS actually is

The same-origin policy blocks JavaScript from reading responses from a different origin (scheme + host + port) unless the server opts in via CORS headers. For non-simple requests the browser first sends a preflight OPTIONS request; the server must answer with the right Access-Control-Allow-* headers before the real request is sent.

Browser (localhost:3000)            Server (localhost:8080)
  --- OPTIONS /api/users ------------>   (preflight)
  <-- 200 + Access-Control-Allow-* ---
  --- GET /api/users ---------------->
  <-- 200 + data ---------------------

Note: CORS is enforced by the browser, not the server. Tools like curl and server-to-server calls are unaffected. CORS is not a substitute for authentication or authorization.

@CrossOrigin — per controller or method

The quickest way to allow a specific origin is @CrossOrigin on a controller or handler.

@RestController
@RequestMapping("/api/users")
@CrossOrigin(origins = "http://localhost:3000")
public class UserController {

    @GetMapping
    public List<UserResponse> all() { ... }

    @PostMapping
    @CrossOrigin(origins = "https://admin.example.com")  // method-level override
    public UserResponse create(@RequestBody CreateUserRequest body) { ... }
}

Common attributes:

@CrossOrigin(
        origins = {"https://app.example.com"},
        methods = {RequestMethod.GET, RequestMethod.POST},
        allowedHeaders = "*",
        allowCredentials = "true",
        maxAge = 3600)

Global configuration with WebMvcConfigurer

For consistent rules across all controllers, register CORS mappings in a WebMvcConfigurer.

import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.*;

@Configuration
public class WebConfig implements WebMvcConfigurer {

    @Override
    public void addCorsMappings(CorsRegistry registry) {
        registry.addMapping("/api/**")
                .allowedOrigins("http://localhost:3000", "https://app.example.com")
                .allowedMethods("GET", "POST", "PUT", "PATCH", "DELETE")
                .allowedHeaders("*")
                .allowCredentials(true)
                .maxAge(3600);
    }
}

Global configuration with CorsConfigurationSource

When you also use Spring Security, the recommended approach is a CorsConfigurationSource bean, which both MVC and Security can pick up.

import org.springframework.context.annotation.*;
import org.springframework.web.cors.*;
import java.util.List;

@Configuration
public class CorsConfig {

    @Bean
    public CorsConfigurationSource corsConfigurationSource() {
        CorsConfiguration config = new CorsConfiguration();
        config.setAllowedOrigins(List.of("http://localhost:3000", "https://app.example.com"));
        config.setAllowedMethods(List.of("GET", "POST", "PUT", "PATCH", "DELETE"));
        config.setAllowedHeaders(List.of("*"));
        config.setAllowCredentials(true);
        config.setMaxAge(3600L);

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

Interaction with Spring Security

Security’s filter chain runs before the dispatcher servlet, so a misconfigured chain will block preflight OPTIONS requests before MVC’s CORS handling sees them. Enable CORS inside the SecurityFilterChain so Security applies your CorsConfigurationSource.

import org.springframework.context.annotation.*;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.web.SecurityFilterChain;

@Configuration
public class SecurityConfig {

    @Bean
    public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
        http
            .cors(cors -> {})   // uses the CorsConfigurationSource bean above
            .csrf(csrf -> csrf.disable())
            .authorizeHttpRequests(auth -> auth.anyRequest().authenticated());
        return http.build();
    }
}

Warning: You cannot combine allowCredentials(true) with allowedOrigins("*") — the browser rejects it. Use allowedOriginPatterns("*") if you genuinely need wildcard origins with credentials.

ApproachScopeBest for
@CrossOriginSingle controller/methodQuick, localized rules
WebMvcConfigurerAll MVC handlersApp-wide rules, no Security
CorsConfigurationSourceMVC + SecurityApps using Spring Security

Verifying a preflight

Request:

curl -i -X OPTIONS http://localhost:8080/api/users \
  -H "Origin: http://localhost:3000" \
  -H "Access-Control-Request-Method: POST"

Output:

HTTP/1.1 200 OK
Access-Control-Allow-Origin: http://localhost:3000
Access-Control-Allow-Methods: GET,POST,PUT,PATCH,DELETE
Access-Control-Allow-Credentials: true
Access-Control-Max-Age: 3600

Pitfalls

  • Forgetting .cors(...) in the SecurityFilterChain makes preflights fail even when MVC CORS is configured.
  • allowedHeaders("*") does not echo credentials-related headers automatically; list custom auth headers explicitly when needed.
  • CORS errors appear in the browser console, not the server log — check the network tab.
Last updated June 13, 2026
Was this helpful?