CORS on Tomcat

⚠️ Security Warning: Using Access-Control-Allow-Origin: * allows any website to access your resources. Always specify exact origins in production.

Apache Tomcat includes built-in support for CORS via the CorsFilter (available since Tomcat 7.0.41). Configure it in your application's web.xml file.

Method 1: Specific Origin (Recommended)

Allow a single trusted origin:

<filter>
  <filter-name>CorsFilter</filter-name>
  <filter-class>org.apache.catalina.filters.CorsFilter</filter-class>

  <init-param>
    <param-name>cors.allowed.origins</param-name>
    <param-value>https://example.com</param-value>
  </init-param>

  <init-param>
    <param-name>cors.allowed.methods</param-name>
    <param-value>GET,POST,PUT,DELETE,OPTIONS</param-value>
  </init-param>

  <init-param>
    <param-name>cors.allowed.headers</param-name>
    <param-value>Content-Type,Authorization,X-Requested-With</param-value>
  </init-param>
</filter>

<filter-mapping>
  <filter-name>CorsFilter</filter-name>
  <url-pattern>/api/*</url-pattern>
</filter-mapping>

Method 2: Multiple Origins

Allow multiple specific origins (issue #146):

<filter>
  <filter-name>CorsFilter</filter-name>
  <filter-class>org.apache.catalina.filters.CorsFilter</filter-class>

  <!-- Comma-separated list of allowed origins -->
  <init-param>
    <param-name>cors.allowed.origins</param-name>
    <param-value>https://example.com,https://app.example.com,https://staging.example.com</param-value>
  </init-param>

  <init-param>
    <param-name>cors.allowed.methods</param-name>
    <param-value>GET,POST,PUT,DELETE,OPTIONS</param-value>
  </init-param>

  <init-param>
    <param-name>cors.allowed.headers</param-name>
    <param-value>Content-Type,Authorization</param-value>
  </init-param>
</filter>

<filter-mapping>
  <filter-name>CorsFilter</filter-name>
  <url-pattern>/api/*</url-pattern>
</filter-mapping>

Method 3: With Credentials

When using cookies or authentication:

<filter>
  <filter-name>CorsFilter</filter-name>
  <filter-class>org.apache.catalina.filters.CorsFilter</filter-class>

  <init-param>
    <param-name>cors.allowed.origins</param-name>
    <param-value>https://example.com</param-value>
  </init-param>

  <!-- Enable credentials (cookies, HTTP auth) -->
  <init-param>
    <param-name>cors.support.credentials</param-name>
    <param-value>true</param-value>
  </init-param>

  <init-param>
    <param-name>cors.allowed.methods</param-name>
    <param-value>GET,POST,PUT,DELETE,OPTIONS</param-value>
  </init-param>
</filter>

<filter-mapping>
  <filter-name>CorsFilter</filter-name>
  <url-pattern>/api/*</url-pattern>
</filter-mapping>

Method 4: Comprehensive Configuration

Full configuration with all common parameters:

<filter>
  <filter-name>CorsFilter</filter-name>
  <filter-class>org.apache.catalina.filters.CorsFilter</filter-class>

  <!-- Allowed origins -->
  <init-param>
    <param-name>cors.allowed.origins</param-name>
    <param-value>https://example.com,https://app.example.com</param-value>
  </init-param>

  <!-- Allowed HTTP methods -->
  <init-param>
    <param-name>cors.allowed.methods</param-name>
    <param-value>GET,POST,PUT,DELETE,OPTIONS</param-value>
  </init-param>

  <!-- Allowed request headers -->
  <init-param>
    <param-name>cors.allowed.headers</param-name>
    <param-value>Content-Type,Authorization,X-Requested-With</param-value>
  </init-param>

  <!-- Exposed response headers (visible to client) -->
  <init-param>
    <param-name>cors.exposed.headers</param-name>
    <param-value>Content-Length,X-Custom-Header</param-value>
  </init-param>

  <!-- Allow credentials (cookies, HTTP auth) -->
  <init-param>
    <param-name>cors.support.credentials</param-name>
    <param-value>true</param-value>
  </init-param>

  <!-- Preflight cache duration (seconds) -->
  <init-param>
    <param-name>cors.preflight.maxage</param-name>
    <param-value>86400</param-value>
  </init-param>
</filter>

<filter-mapping>
  <filter-name>CorsFilter</filter-name>
  <url-pattern>/api/*</url-pattern>
</filter-mapping>

Method 5: Origin Pattern Matching (Regex)

Allow any subdomain of example.com using regex:

<filter>
  <filter-name>CorsFilter</filter-name>
  <filter-class>org.apache.catalina.filters.CorsFilter</filter-class>

  <!-- Use regex to match origins -->
  <init-param>
    <param-name>cors.allowed.origins</param-name>
    <param-value>*</param-value>
  </init-param>

  <init-param>
    <param-name>cors.allowed.origins.regex</param-name>
    <param-value>https?://(.+\.)?example\.com</param-value>
  </init-param>

  <init-param>
    <param-name>cors.allowed.methods</param-name>
    <param-value>GET,POST,PUT,DELETE,OPTIONS</param-value>
  </init-param>
</filter>

<filter-mapping>
  <filter-name>CorsFilter</filter-name>
  <url-pattern>/api/*</url-pattern>
</filter-mapping>

Method 6: Path-Specific CORS

Apply different CORS policies to different paths:

<!-- Restricted CORS for API endpoints -->
<filter>
  <filter-name>ApiCorsFilter</filter-name>
  <filter-class>org.apache.catalina.filters.CorsFilter</filter-class>
  <init-param>
    <param-name>cors.allowed.origins</param-name>
    <param-value>https://example.com</param-value>
  </init-param>
</filter>

<filter-mapping>
  <filter-name>ApiCorsFilter</filter-name>
  <url-pattern>/api/*</url-pattern>
</filter-mapping>

<!-- Open CORS for public endpoints -->
<filter>
  <filter-name>PublicCorsFilter</filter-name>
  <filter-class>org.apache.catalina.filters.CorsFilter</filter-class>
  <init-param>
    <param-name>cors.allowed.origins</param-name>
    <param-value>*</param-value>
  </init-param>
</filter>

<filter-mapping>
  <filter-name>PublicCorsFilter</filter-name>
  <url-pattern>/public/*</url-pattern>
</filter-mapping>

Common Filter Parameters

Parameter Description Default
cors.allowed.origins Comma-separated allowed origins * (any)
cors.allowed.origins.regex Regex pattern for origins None
cors.allowed.methods Allowed HTTP methods GET,POST,HEAD,OPTIONS
cors.allowed.headers Request headers allowed *
cors.exposed.headers Response headers exposed to client None
cors.support.credentials Enable credentials false
cors.preflight.maxage Preflight cache (seconds) 1800 (30 min)

Important: CORS filter should be placed early in the filter chain, before authentication/authorization filters, so that preflight OPTIONS requests don't get rejected.

For full documentation, see:

Testing Your CORS Configuration

For comprehensive testing instructions including curl commands, browser DevTools usage, and troubleshooting common CORS errors, see the CORS Testing Guide.

Who’s behind this

Monsur Hossain and Michael Hausenblas

Contribute

The content on this site stays fresh thanks to help from users like you! If you have suggestions or would like to contribute, fork us on GitHub.

Buy the book

Save 39% on CORS in Action with promotional code hossainco at manning.com/hossain