Back to Blog
Solving Spring Boot CORS Errors Once and For All
Java Engineering

Solving Spring Boot CORS Errors Once and For All

The definitive guide to fixing Access-Control-Allow-Origin errors in Spring Boot. Learn the difference between @CrossOrigin, Global Config, and Security Filters.

December 22, 20253 min readRabi
javaspring-bootcorssecurityweb-development

If you are a full-stack developer, this error haunts your dreams:

Access to fetch at http://localhost:8080/api from origin http://localhost:3000 has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource.

You just want to connect your React frontend to your Spring Boot backend. Why is the browser stopping you?

CORS Nightmare Debug

What is CORS? (It's not a bug)

CORS (Cross-Origin Resource Sharing) is a security feature protecting the browser, not the server.

Without CORS, a malicious site (evil.com) could make a request to bank.com content while you are logged in. The browser blocks cross-origin requests by default unless the server explicitly says "It's okay, I trust evil.com".

The Preflight Check

Before sending a specialized request (like a POST with JSON), the browser sends a "Preflight" OPTIONS request.

CORS Handshake Flow

If your server doesn't reply to OPTIONS with 200 OK and the correct Access-Control-Allow-Origin headers, the browser blocks the real request.

Solution 1: Global Configuration (The Best Way)

If you want to apply rules to your entire application, implement WebMvcConfigurer. This is the cleanest approach.

@Configuration
public class CorsConfig implements WebMvcConfigurer {
 
    @Override
    public void addCorsMappings(CorsRegistry registry) {
        registry.addMapping("/**") // Apply to all endpoints
            .allowedOrigins("http://localhost:3000", "https://mydomain.com")
            .allowedMethods("GET", "POST", "PUT", "DELETE", "OPTIONS")
            .allowedHeaders("*")
            .allowCredentials(true)
            .maxAge(3600); // Cache the preflight response for 1 hour
    }
}

Why this is good: It keeps your configuration central. You define your frontend URL in one place (perfect for pulling from application.properties).

Solution 2: @CrossOrigin (The Quick Way)

If you only need to open up a single specific endpoint, you can annotate the controller.

@RestController
@RequestMapping("/api/public")
// Allow ALL origins (Not recommended for prod)
@CrossOrigin(origins = "*") 
public class PublicController {
 
    @GetMapping("/hello")
    public String hello() {
        return "Hello World";
    }
}

Why this is bad: It clutters your business code. Code duplication. If you have 50 controllers, do you paste this 50 times?

Solution 3: Spring Security (The "Gotcha")

If you are using Spring Security, the solutions above might NOT WORK.

Why? Because Spring Security sits before Spring MVC in the filter chain. It will intercept the OPTIONS request and reject it (401 Unauthorized) before it ever reaches your WebMvcConfigurer settings.

You must configure CORS inside the SecurityFilterChain.

@Configuration
@EnableWebSecurity
public class SecurityConfig {
 
    @Bean
    public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
        http
            // 1. Enable CORS in Security
            .cors(Customizer.withDefaults())
            
            // 2. Disable CSRF (Usually needed for stateless APIs)
            .csrf(csrf -> csrf.disable())
            
            .authorizeHttpRequests(auth -> auth
                .anyRequest().authenticated()
            );
 
        return http.build();
    }
 
    // 3. Define the source
    @Bean
    public CorsConfigurationSource corsConfigurationSource() {
        CorsConfiguration configuration = new CorsConfiguration();
        configuration.setAllowedOrigins(List.of("http://localhost:3000"));
        configuration.setAllowedMethods(List.of("GET", "POST", "PUT", "DELETE"));
        configuration.setAllowedHeaders(List.of("*"));
        configuration.setAllowCredentials(true);
        
        UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
        source.registerCorsConfiguration("/**", configuration);
        return source;
    }
}

Summary Checklist

  1. Just Spring Boot? Use WebMvcConfigurer (Solution 1).
  2. Using Spring Security? Use cors(Customizer.withDefaults()) and a CorsConfigurationSource Bean (Solution 3).
  3. Authentication Cookies? You MUST set .allowCredentials(true) AND you cannot use * for origins. You must specify the exact domain.

CORS errors are frustrating, but they are just the browser trying to keep your users safe. Configure it once, centrally, and sleep soundly.

Happy Coding! ☕

Join the Community

Get the latest articles on system design, frontend & backend development, and emerging tech trends delivered straight to your inbox.

No spam. Unsubscribe at any time.

Liked the blog?

Share it with your friends and help them learn something new!