React · Spring Boot

How to fix CORS errors between React and Spring Boot

Fix “blocked by CORS policy” errors between a React front end and a Spring Boot API: MVC config, Spring Security preflight, dev proxies, credentials and debugging.

By the JobSupport4U engineering team · Updated · 6 min read

The request works in Postman. In the browser, your React app gets this instead:

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

Nothing is wrong with your fetch code. The browser is enforcing a security rule that Postman ignores, and the fix belongs on the Spring Boot side — or in how you serve the two apps.

Why it happens

Browsers block JavaScript from reading responses from a different origin — a different scheme, host or port — unless the server explicitly allows it with CORS headers. localhost:5173 (your React dev server) and localhost:8080 (Spring Boot) are different origins, so the browser asks Spring Boot for permission first.

For many requests — anything sending JSON with Content-Type: application/json, or an Authorization header — the browser first sends a preflight OPTIONS request. If that preflight doesn’t come back successful with the right headers, the real request is never sent.

Fix 1: allow your front end in Spring MVC

For a single controller, @CrossOrigin(origins = "http://localhost:5173") is enough. For the whole API, configure it once:

Java · Spring Boot
@Configuration
public class CorsConfig implements WebMvcConfigurer {
  @Override
  public void addCorsMappings(CorsRegistry registry) {
    registry.addMapping("/api/**")
        .allowedOrigins("http://localhost:5173", "https://app.example.com")
        .allowedMethods("GET", "POST", "PUT", "DELETE", "OPTIONS")
        .allowedHeaders("Authorization", "Content-Type")
        .allowCredentials(true);
  }
}

Fix 2: if you use Spring Security

This is why “I added @CrossOrigin and it still fails” happens. Spring Security’s filters run before Spring MVC, and they reject the unauthenticated preflight with a 401 or 403 before your CORS settings are ever consulted. Enable CORS in the security chain and give it a configuration source:

Java · Spring Security 6
@Bean
SecurityFilterChain api(HttpSecurity http) throws Exception {
  http
      .cors(Customizer.withDefaults())
      .authorizeHttpRequests(auth -> auth.anyRequest().authenticated())
      .oauth2ResourceServer(oauth -> oauth.jwt(Customizer.withDefaults()));
  return http.build();
}

@Bean
CorsConfigurationSource corsConfigurationSource() {
  var config = new CorsConfiguration();
  config.setAllowedOrigins(List.of("http://localhost:5173", "https://app.example.com"));
  config.setAllowedMethods(List.of("GET", "POST", "PUT", "DELETE", "OPTIONS"));
  config.setAllowedHeaders(List.of("Authorization", "Content-Type"));
  config.setAllowCredentials(true);

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

With cors(Customizer.withDefaults()), Spring Security picks up the corsConfigurationSource bean and answers preflight requests before authentication runs.

Fix 3: avoid CORS in development with a proxy

If the browser only ever talks to one origin, there is no CORS at all. Let your dev server forward API calls to Spring Boot:

vite.config.js
export default {
  server: {
    proxy: {
      "/api": "http://localhost:8080",
    },
  },
};

Then call fetch("/api/orders") with a relative URL. (Create React App projects use the proxy field in package.json for the same effect.) In production, serving the React app and the API under the same domain — through a reverse proxy or Kubernetes Ingress — removes the need for CORS in the same way.

Cookies and credentials

  • To send cookies cross-origin, the browser needs credentials: "include" on the fetch call and allowCredentials(true) on the server.
  • With credentials allowed, the allowed origin can’t be "*" — Spring rejects that combination at startup. List your origins, or use allowedOriginPatterns if you need wildcards such as https://*.example.com.
  • Cross-site cookies also need SameSite=None; Secure, which means HTTPS.

Debugging checklist

  1. Open DevTools → Network, find the OPTIONS preflight and check its status. A 401, 403 or 404 there means the server is rejecting the preflight — usually Spring Security.
  2. Compare the origin exactly: http vs https, localhost vs 127.0.0.1, and the port all matter.
  3. Look at the real response status. A 500 error often comes back without CORS headers, so the browser reports a CORS error while the actual problem is an exception on the server — check the Spring logs.
  4. Make sure every custom header you send (for example X-Request-Id) is in the allowed headers.
Keep reading

More guides