Springboot
HTTP 403
POST request
OPTIONS method
CORS issue

Springboot endpoint 403 OPTIONS when doing a POST request

System Design practice on Codemia

Work through 120+ system design problems with detailed solutions, from rate limiters to multi-region storage.

Practice system design

Introduction

If a browser sends a POST request to a Spring Boot API and you see a 403 on the preceding OPTIONS request, the problem is usually not the POST endpoint itself. It is almost always a CORS preflight request being blocked by Spring Security or by incomplete CORS configuration before the real POST is even allowed to leave the browser.

Why the Browser Sends OPTIONS First

Browsers send a preflight OPTIONS request when a cross-origin request uses methods, headers, or content types that require permission checks. The browser asks the server whether the later POST is allowed from that origin.

A typical preflight request asks questions like:

  • is this origin allowed
  • is POST allowed
  • are custom headers such as Authorization or Content-Type allowed

If the server responds without the expected CORS headers, or if security blocks OPTIONS, the browser never sends the actual POST.

The Usual Spring Boot Cause

In many Spring Boot apps, CORS is configured in the controller or MVC layer but not in Spring Security. When Security runs first, it can reject the preflight request with 403 before the CORS configuration ever gets a chance to respond.

The modern fix is to enable CORS in the security filter chain and provide an explicit CorsConfigurationSource.

java
1import org.springframework.context.annotation.Bean;
2import org.springframework.context.annotation.Configuration;
3import org.springframework.http.HttpMethod;
4import org.springframework.security.config.Customizer;
5import org.springframework.security.config.annotation.web.builders.HttpSecurity;
6import org.springframework.security.web.SecurityFilterChain;
7import org.springframework.web.cors.CorsConfiguration;
8import org.springframework.web.cors.CorsConfigurationSource;
9import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
10
11import java.util.List;
12
13@Configuration
14public class SecurityConfig {
15
16    @Bean
17    SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
18        http
19            .cors(Customizer.withDefaults())
20            .csrf(csrf -> csrf.disable())
21            .authorizeHttpRequests(auth -> auth
22                .requestMatchers(HttpMethod.OPTIONS, "/**").permitAll()
23                .anyRequest().authenticated()
24            );
25
26        return http.build();
27    }
28
29    @Bean
30    CorsConfigurationSource corsConfigurationSource() {
31        CorsConfiguration config = new CorsConfiguration();
32        config.setAllowedOrigins(List.of("http://localhost:3000"));
33        config.setAllowedMethods(List.of("GET", "POST", "PUT", "DELETE", "OPTIONS"));
34        config.setAllowedHeaders(List.of("Authorization", "Content-Type"));
35        config.setAllowCredentials(true);
36
37        UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
38        source.registerCorsConfiguration("/**", config);
39        return source;
40    }
41}

This does two important things. It tells Spring Security to process CORS, and it gives the framework enough information to answer the preflight request correctly.

@CrossOrigin Helps, But It Is Not Always Enough

For simple applications, @CrossOrigin on a controller can work:

java
1import org.springframework.web.bind.annotation.CrossOrigin;
2import org.springframework.web.bind.annotation.PostMapping;
3import org.springframework.web.bind.annotation.RestController;
4
5@RestController
6@CrossOrigin(origins = "http://localhost:3000")
7public class DemoController {
8
9    @PostMapping("/api/items")
10    public String create() {
11        return "ok";
12    }
13}

However, if Spring Security is active and not configured to allow or process preflight requests, @CrossOrigin alone may still leave you with a 403. That is why the security-layer configuration is usually the real fix.

How to Debug It Quickly

A useful debugging pattern is to inspect the failed OPTIONS request in browser developer tools. Check the request headers and then check whether the response includes:

  • 'Access-Control-Allow-Origin'
  • 'Access-Control-Allow-Methods'
  • 'Access-Control-Allow-Headers'

If those headers are missing, the server is not answering CORS correctly. If the request works in Postman but fails in the browser, that is another strong sign that CORS preflight is the issue. Postman is not bound by browser CORS rules.

Common Pitfalls

The biggest pitfall is focusing on the POST controller while ignoring the preflight request. If OPTIONS fails, the controller method is never reached.

Another common mistake is allowing the origin but forgetting the headers. A frontend that sends Authorization or JSON content typically needs those headers listed explicitly.

Developers also run into trouble by mixing wildcard origins with credentials. Browsers do not allow credentialed cross-origin requests when the server responds with a wildcard origin.

Finally, do not assume a 403 from the browser means your business authorization failed. In many cases, it is simply the CORS handshake being rejected earlier in the filter chain.

Summary

  • A 403 on OPTIONS before a POST is usually a blocked CORS preflight request.
  • Configure CORS in Spring Security, not only at the controller layer.
  • Permit OPTIONS requests and return the required Access-Control-Allow-* headers.
  • Use browser dev tools to inspect the preflight response directly.
  • If Postman works but the browser does not, CORS is the first thing to verify.

Related reading
Course
Beginner
27 lessons
10 hours
System Design Fundamentals

Build a strong foundation in designing scalable, reliable distributed systems.

View the course
Track what you have practised

A free account saves your progress, solutions and study plan across every problem on Codemia.

System Design practice on Codemia

Work through 120+ system design problems with detailed solutions, from rate limiters to multi-region storage.

Practice system design

All Rights Reserved.