Skip to content

WebMvcConfigurer addCorsMappings exposed headers doesn’t work

I want to return an ETag header, but my Client cannot read it because it is not exposed. I have the following code:

public class WebConfig implements WebMvcConfigurer {
    public void addCorsMappings(@NonNull CorsRegistry registry) {

    public ShallowEtagHeaderFilter shallowEtagHeaderFilter() {
        return new ShallowEtagHeaderFilter();

But the client still cannot read the ETag. The only thing that works is the following:

            @ApiResponse(code = 200, message = "OK"),
            @ApiResponse(code = 500, message = "System Error")
    @ApiOperation(value = "returns the meals", response = MealDTO.class, responseContainer = "List", produces = MediaType.APPLICATION_JSON_VALUE)
    public List<MealDTO> getMeals(
            @ApiParam(name = "page", type = "Integer", value = "Number of the page", example = "2")
            @RequestParam(required = false) Integer page,
            @ApiParam(name = "size", type = "Integer", value = "The size of one page", example = "5")
            @RequestParam(required = false) Integer size,
            @ApiParam(name = "sortBy", type = "String", value = "sort criteria", example = "name.asc,price.desc")
            @RequestParam(required = false) String sortBy,
            @ApiParam(name = "userId", type = "long", value = "ID of the User", example = "-1")
            @PathVariable Long userId,
            HttpServletResponse response
    ) {
        log.debug("Entered class = MealController & method = getMeals");
        response.setHeader("Access-Control-Expose-Headers", "ETag");
        return this.mealService.getMealsByUserId(page, size, sortBy, userId);

Manually setting the exposed header for each endpoint. Isn’t this wat Cors Mapping was supposed to do? ExposedHeaders simply don’t work for me at all.


From the comment below I saw that it may have something to do with the WebSecurityConfigurerAdapter, I’m also adding that:

@EnableGlobalMethodSecurity(prePostEnabled = true)
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
    private final UserDetailsService userDetailsService;

    public WebSecurityConfig(@Qualifier("userServiceImpl") UserDetailsService userDetailsService) {
        this.userDetailsService = userDetailsService;

    protected void configure(AuthenticationManagerBuilder auth) throws Exception {

    protected void configure(HttpSecurity http) throws Exception {
                .addFilter(new AuthenticationFilter(authenticationManager()))
                .addFilter(new AuthorizationFilter(authenticationManager()))

//        http.headers().cacheControl().disable();

    //region Beans
    public AuthenticationManager authenticationManagerBean() throws Exception {
        return super.authenticationManagerBean();

    public PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();

    CorsConfigurationSource corsConfigurationSource() {
        CorsConfiguration configuration = new CorsConfiguration();
        UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
        source.registerCorsConfiguration("/**", configuration);
        return source;


Showing how I’m making calls from my client:

export const getMealByIdApi: (mealId: number, etag: string | null) => Promise<Meal | void> = (mealId, etag) => {
    let _config = config;
    if (etag) {
        _config.headers['If-None-Match'] = etag;

    return axiosInstance
        .get<Meal>(`/meal/${mealId}`, _config)
        .then(response => {
            log(`[getMealByIdApi] [${response.status}] Successful API call for meal with id ${mealId}.`);
            const result: Meal =;
            result.etag = response.headers['etag'];
            return result;
        .catch(err => {
            if (err.response.status === 304) {
                log(`[getMealByIdApi] [304] Successful API call for meal with id ${mealId}.`);

            throw err;

I’m getting no etag in the headers if I don’t specify explicitly in the endpoint: response.setHeader(“Access-Control-Expose-Headers”, “ETag”);



According to Axios get access to response header fields. we need to add “ETag” to “Access-Control-Expose-Headers”, such that we can access response.headers['etag']. Since you have already added ShallowEtagHeaderFilter and also exposed “etag” in addCorsMappings. “ETag” should be added to response header “Access-Control-Expose-Headers” for CORS request.

To make sure you request is CORS, you can debug DefaultCorsProcessor#processRequest method, and check if CorsUtils.isCorsRequest(request) returning true.

public boolean processRequest(@Nullable CorsConfiguration config, HttpServletRequest request,
        HttpServletResponse response) throws IOException {

    response.addHeader(HttpHeaders.VARY, HttpHeaders.ORIGIN);
    response.addHeader(HttpHeaders.VARY, HttpHeaders.ACCESS_CONTROL_REQUEST_METHOD);
    response.addHeader(HttpHeaders.VARY, HttpHeaders.ACCESS_CONTROL_REQUEST_HEADERS);

    if (!CorsUtils.isCorsRequest(request)) {
        return true;  // will not add response header if not CORS
    ...add response header

If it is returning false, you can check the requirement of CORS request from CorsUtils#isCorsRequest description:

Returns true if the request is a valid CORS one by checking Origin header presence and ensuring that origins are different.