Spring Boot
HTTP OPTIONS
REST API
CORS
Spring Framework

How to handle HTTP OPTIONS requests in Spring Boot?

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

HTTP OPTIONS requests are used to discover allowed methods and are central to browser CORS preflight behavior. In Spring Boot, mishandled OPTIONS requests often appear as frontend CORS failures even when main endpoints work. Correct handling requires alignment between MVC routing, security rules, and CORS configuration.

Why OPTIONS Requests Matter

Browsers send a preflight OPTIONS request before certain cross-origin calls. If this preflight fails, the actual request is never sent. That means backend logs may show only OPTIONS failures while business endpoints seem fine.

A minimal controller-level mapping can handle OPTIONS directly:

java
1import org.springframework.http.HttpHeaders;
2import org.springframework.http.ResponseEntity;
3import org.springframework.web.bind.annotation.*;
4
5@RestController
6@RequestMapping("/api/items")
7public class ItemController {
8
9    @RequestMapping(method = RequestMethod.OPTIONS)
10    public ResponseEntity<Void> options() {
11        return ResponseEntity.noContent()
12                .header(HttpHeaders.ALLOW, "GET,POST,PUT,DELETE,OPTIONS")
13                .build();
14    }
15
16    @GetMapping
17    public String list() {
18        return "ok";
19    }
20}

This is explicit, but global CORS config is often cleaner for larger apps.

Global CORS Configuration

A centralized CORS configuration avoids duplicated annotations and makes policy easier to audit.

java
1import org.springframework.context.annotation.Bean;
2import org.springframework.context.annotation.Configuration;
3import org.springframework.web.cors.CorsConfiguration;
4import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
5import org.springframework.web.filter.CorsFilter;
6
7import java.util.List;
8
9@Configuration
10public class CorsConfig {
11
12    @Bean
13    public CorsFilter corsFilter() {
14        CorsConfiguration config = new CorsConfiguration();
15        config.setAllowedOrigins(List.of("http://localhost:3000"));
16        config.setAllowedMethods(List.of("GET", "POST", "PUT", "DELETE", "OPTIONS"));
17        config.setAllowedHeaders(List.of("*"));
18        config.setAllowCredentials(true);
19
20        UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
21        source.registerCorsConfiguration("/**", config);
22        return new CorsFilter(source);
23    }
24}

With this in place, preflight requests are answered consistently.

Spring Security Interaction

If Spring Security blocks OPTIONS requests, CORS config alone is not enough. Permit OPTIONS paths explicitly.

java
1import org.springframework.context.annotation.Bean;
2import org.springframework.context.annotation.Configuration;
3import org.springframework.http.HttpMethod;
4import org.springframework.security.config.annotation.web.builders.HttpSecurity;
5import org.springframework.security.web.SecurityFilterChain;
6
7@Configuration
8public class SecurityConfig {
9
10    @Bean
11    SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
12        http
13            .csrf(csrf -> csrf.disable())
14            .authorizeHttpRequests(auth -> auth
15                .requestMatchers(HttpMethod.OPTIONS, "/**").permitAll()
16                .anyRequest().authenticated()
17            );
18        return http.build();
19    }
20}

This step resolves many real-world preflight failures.

Testing OPTIONS Endpoints

Add integration tests for preflight behavior, not only business methods.

java
1import org.junit.jupiter.api.Test;
2import org.springframework.beans.factory.annotation.Autowired;
3import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
4import org.springframework.boot.test.context.SpringBootTest;
5import org.springframework.test.web.servlet.MockMvc;
6
7import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.options;
8import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
9
10@SpringBootTest
11@AutoConfigureMockMvc
12class CorsOptionsTest {
13
14    @Autowired
15    MockMvc mvc;
16
17    @Test
18    void preflightIsAllowed() throws Exception {
19        mvc.perform(options("/api/items")).andExpect(status().isNoContent());
20    }
21}

This prevents CORS regressions during security refactors.

Controller Annotation Alternative

For smaller services, @CrossOrigin at controller or method level may be enough. This is less centralized but useful for scoped policies.

java
1import org.springframework.web.bind.annotation.CrossOrigin;
2import org.springframework.web.bind.annotation.GetMapping;
3import org.springframework.web.bind.annotation.RestController;
4
5@RestController
6@CrossOrigin(origins = "http://localhost:3000", methods = {
7        org.springframework.web.bind.annotation.RequestMethod.GET,
8        org.springframework.web.bind.annotation.RequestMethod.POST,
9        org.springframework.web.bind.annotation.RequestMethod.OPTIONS
10})
11public class PingController {
12
13    @GetMapping("/api/ping")
14    public String ping() {
15        return "pong";
16    }
17}

When policy grows, migrate this local annotation style to global config for consistency.

Proxy and Gateway Considerations

OPTIONS requests may be intercepted by API gateways or reverse proxies before reaching Spring Boot. Confirm that infrastructure layers forward preflight headers unchanged.

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

Inspect response headers for allowed methods and origins. This direct check narrows whether issue is in app code or infrastructure.

Practical Debug Workflow

A practical sequence is:

  • Verify direct OPTIONS response from service.
  • Verify security permit rules.
  • Verify browser request headers in dev tools.
  • Verify gateway behavior in staging.

Following this sequence avoids random config changes and shortens troubleshooting time.

Common Pitfalls

  • Configuring CORS on controllers but not permitting OPTIONS in security rules.
  • Allowing methods list that excludes OPTIONS.
  • Testing API with Postman only and missing browser preflight behavior.
  • Using wildcard origins with credentials and violating browser rules.
  • Applying conflicting CORS settings in multiple config locations.

Summary

  • OPTIONS handling is critical for browser CORS preflight success.
  • Use centralized CORS policy where possible.
  • Ensure Spring Security permits OPTIONS requests.
  • Add tests for preflight paths, not only main endpoints.
  • Keep CORS and security configuration aligned.

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.