The specific problem here is the filter configuration:
<filter-mapping>
<filter-name>struts2</filter-name>
<url-pattern>*.action</url-pattern>
</filter-mapping>
The recommended configuration (unless you specifically know what you're doing) is to map to *
:
<filter-mapping>
<filter-name>struts2</filter-name>
<url-pattern>*</url-pattern>
</filter-mapping>
If you map only to *.action
, non-action requests (like CSS and JavaScript) won't be processed by the filter. S2 examines the request for files it (S2) knows about, like its own CSS and JavaScript files, and serves those requests itself even though they're not action requests.
This is documented in the S2 guides' Static Content section.
It's perfectly valid to map to *.action
, but then you do need to extract the static files and put them at their required location, at least if you intend to use the default S2 themes/JavaScript. That's also optional: the framework is designed to get you started relatively quickly, but if you have specific needs that the framework doesn't handle, using that part of S2 may or may not be the best choice.