Configuration: struts.xml & Constants
|
This content was generated with the assistance of AI and should be verified against the official documentation before being relied on in production. The reference material consulted while preparing these pages is listed in the section bibliography. |
This page is written against Apache Struts 7.4.0 (released 2026-10-02, the current line together with 6.12.0), which runs on
Jakarta Servlet 6.0 or later and so on Apache Tomcat 11.0.26 with Java 21 (Struts 7 needs Java 17 or later). Everything was
verified on 2026-10-08: the facts against the Struts documentation pages named in the references, the default.properties
and struts-default.xml that ship inside struts2-core-7.4.0.jar and the 7.4.0 source of the Dispatcher; every example
by building it into a Maven WAR (the struts2-bom 7.4.0 and struts2-core, no other Struts artifact) and running it on
Tomcat 11.0.26 with curl. The outputs shown are the outputs of that run, and rows marked "observed" were reproduced there.
The running scenario is the Bookshelf: an action that lists books grows from one struts.xml into a configuration
split over several files, a plugin, a few constants and two implementations of a catalogue bean.
Struts is configured almost entirely by one XML file, struts.xml, plus a flat list of named settings called constants.
The same constants can come from five different places and the framework has a fixed order in which it reads them, so the
most common configuration bug is not a typo but a value that is set in one place and silently overridden in another. This
page is the reference for that machinery. The elements that describe the application itself have pages of their own:
packages and namespaces, actions, wildcard mappings, results, interceptors and exception mappings. Here they appear only as
the skeleton the configuration files carry.
The Configuration Files
A Struts application needs exactly one configuration file, and it is not a Struts file: web.xml must register the
StrutsPrepareAndExecuteFilter (see Servlet Fundamentals for filters and the
deployment descriptor). Everything else is optional, because the framework ships with its own configuration and every
setting has a default. The documentation lists the files like this.
| File | Optional | Location | Purpose |
|---|---|---|---|
|
no |
|
The deployment descriptor: registers the filter, its init-params and its mapping |
|
yes |
|
Main configuration: packages, actions, results, interceptors, includes, beans and constants |
|
yes |
inside |
The framework defaults for every constant (see default.properties and struts.properties for what the "optional" really means) |
|
yes |
inside |
The configuration Struts supplies: the |
|
yes |
the root of a plugin JAR |
Configuration for a plugin, in the same format as |
|
yes |
the root of a plugin JAR |
Like |
|
yes |
|
Overrides the default Velocity configuration |
|
yes |
|
Default Velocity macros referenced by |
Two more files are loaded from the class path although the table does not list them: struts.properties, the legacy
properties file (struts.properties, the Legacy File), and the two files struts-default.xml itself includes, struts-beans.xml
(the framework’s own beans) and struts-excluded-classes.xml (the classes and packages OGNL may never touch). The same page describes
how static content (the JavaScript and CSS of the UI tags) is served by the filter, which matters for the filter mapping
(The Filter and Its Init-Params in web.xml).
Source: Struts — Configuration Files
Source: Struts — Configuration Elements
Load Order: Who Wins
The Dispatcher (the object the filter creates in its init method) builds the configuration by registering
providers one after another. For XML elements that define packages, later and earlier files are merged; for constants,
which are plain name and value pairs, the provider registered last wins. The 7.4.0 Dispatcher.init() registers them
in this order.
| Step | Source | What it contributes |
|---|---|---|
1 |
|
Every constant with its default value |
2 |
|
The XML configuration; constants declared with |
3 |
|
Optional |
4 |
|
The legacy properties file; overrides every XML constant |
5 |
|
Optional |
6 |
The filter init-params of |
Every init-param is copied into the constants; they override step 4 |
7 |
|
Turns the final constants into bean selections (which |
8 |
Every |
Configuration that depends on all of the above, for plugin extension points |
(core JAR)"] --> sd["struts-default.xml
(includes struts-beans.xml)"] sd --> sp["struts-plugin.xml
(every plugin JAR)"] sp --> sx["struts.xml
(+ include files)"] sx --> jc["javaConfigClasses
(optional)"] jc --> pr["struts.properties"] pr --> cp["configProviders
(optional)"] cp --> ip["web.xml filter init-params"] ip --> bs["bean selection and
devMode switch"] bs --> df["struts-deferred.xml
(plugin JARs)"] df --> end1["The one runtime configuration"]
The documentation page "Constant Configuration" gives a shorter list ("struts-default.xml, struts-plugin.xml, struts.xml,
default.properties, web.xml, struts-deferred.xml") and places default.properties after struts.xml; the "Plugins
Architecture" page gives the XML part correctly (default, plugins, struts.xml, deferred). The 7.4.0 source and the
experiment below show that the defaults file is read first, which is why a value in struts.xml can override it.
Source: Struts — Plugins Architecture
Seeing the order in the Bookshelf
The same constant is defined in the plugin JAR, in struts.xml, in struts.properties and as a filter init-param, and
an action reads the effective value through the container.
package org.example.bookshelf;
import java.util.List;
import org.apache.struts2.ActionSupport;
import org.apache.struts2.inject.Inject;
public class ConfigAction extends ActionSupport {
@Inject("struts.devMode")
private String devMode;
@Inject("bookshelf.pageSize")
private String pageSize;
@Inject("bookshelf.order")
private String order;
@Inject("sorted")
private Catalog catalog;
public String getDevMode() { return devMode; }
public String getPageSize() { return pageSize; }
public String getOrder() { return order; }
public List<String> getTitles() { return catalog.titles(); }
}
Source: Struts 7 Javadoc — @Inject
The action is mapped to a plain-text JSP (/config) that prints the four properties with <s:property value="order"/>.
Adding one source at a time gave these results.
Where the constant bookshelf.pageSize / bookshelf.order is set |
pageSize |
order |
|---|---|---|
|
20 |
|
plus |
50 |
|
plus filter init-params in |
7 |
|
an application file |
20 (ignored) |
|
an application file |
99 |
|
The plugin, struts.xml, struts.properties, init-param order of precedence is the order of the table in the previous
section, and the fourth row is the one that surprises people (default.properties and struts.properties).
Source: Struts — Constant Configuration
struts.xml
struts.xml is the main file. It must be on the class path (in a Maven project, src/main/resources/struts.xml, which
ends up in WEB-INF/classes), and it begins with a DOCTYPE that names a Struts DTD. The DTD is validating: the parser
rejects an element in the wrong place when the application starts.
| Element | Meaning |
|---|---|
|
Groups actions, results, interceptors and exception mappings; |
|
Loads another file in |
|
Registers a class in the internal container (Bean Configuration) |
|
Sets one constant (Constants) |
|
Names a |
|
Lists the |
The content model is (package|include|bean|constant)*, bean-selection?, unknown-handler-stack?: packages, includes, beans
and constants may be mixed in any order, but bean-selection and unknown-handler-stack must come last. The root element
also takes an optional order attribute (Include Configuration).
The newest DTD is struts-6.0.dtd, and it is the right DOCTYPE for Struts 7.4: a request for
https://struts.apache.org/dtds/struts-7.0.dtd returns 404 (checked 2026-10-08), and the file shipped inside
struts2-core-7.4.0.jar (struts-default.xml) itself declares Struts Configuration 6.0.
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE struts PUBLIC
"-//Apache Software Foundation//DTD Struts Configuration 6.0//EN"
"https://struts.apache.org/dtds/struts-6.0.dtd">
<struts>
<constant name="struts.devMode" value="${env.STRUTS_DEV_MODE:false}"/>
<constant name="struts.configuration.xml.reload" value="true"/>
<constant name="bookshelf.pageSize" value="20"/>
<constant name="bookshelf.order" value="struts.xml"/>
<bean type="org.example.bookshelf.Catalog" name="inMemory" class="org.example.bookshelf.InMemoryCatalog"/>
<bean type="org.example.bookshelf.Catalog" name="sorted" class="org.example.bookshelf.SortedCatalog"/>
<bean type="org.apache.struts2.UnknownHandler" name="shelf" class="org.example.bookshelf.ShelfUnknownHandler"/>
<include file="bookshelf/struts-*.xml"/>
<package name="default" extends="struts-default" namespace="/">
<action name="books" class="org.example.bookshelf.BookAction">
<result>/WEB-INF/views/books.jsp</result>
</action>
<action name="config" class="org.example.bookshelf.ConfigAction">
<result>/WEB-INF/views/config.jsp</result>
</action>
</package>
<unknown-handler-stack>
<unknown-handler-ref name="shelf"/>
</unknown-handler-stack>
</struts>
The package extends struts-default, the abstract package of struts-default.xml that supplies the result types and the
interceptor stacks (struts-default.xml); an action in a package that does not extend it has no defaultStack.
Source: Struts — struts.xml
Source: Struts Configuration DTD 6.0
Include Configuration
The "divide and conquer" strategy for large configurations is the <include> element. The file attribute is a class path
resource name, resolved from the root of the class path (not relative to the including file), and each included file must
have the same format as struts.xml, including the DOCTYPE. In a team, each module owns its file.
<include file="bookshelf/struts-*.xml"/>
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE struts PUBLIC
"-//Apache Software Foundation//DTD Struts Configuration 6.0//EN"
"https://struts.apache.org/dtds/struts-6.0.dtd">
<struts>
<package name="admin" extends="struts-default" namespace="/admin">
<action name="stats" class="org.example.bookshelf.BookAction">
<result>/WEB-INF/views/books.jsp</result>
</action>
</package>
</struts>
$ curl -s -o /dev/null -w "%{http_code}\n" http://localhost:18080/g8conf/admin/stats
200
The documentation shows only literal file names; the 7.4.0 XmlConfigurationProvider also accepts a pattern with *,
as the example does, and resolves it against the class path when the file loads. Four behaviours of includes follow from the 7.4.0 source; the last was also observed.
-
A file is loaded once: a second
<include>of the same name is ignored (source). -
A missing file is skipped without an error (the provider logs "Ignoring file that does not exist" at debug level), so a misspelled include silently drops a whole module. Check that the module’s actions answer.
-
The
orderattribute of the root<struts order="10">is read as an integer, and documents are sorted by it before their includes are expanded; a missing or invalid value counts asInteger.MAX_VALUE, so the default is "last". Use it only to control the loading order between files of the same name, for example thestruts-plugin.xmlof two plugins. -
With
struts.configuration.xml.reloadon, adding a new file that matches the pattern is not noticed until one of the files the framework already tracks changes; after touching a tracked file, the new package answered (observed, Reloading and Development Mode).
Source: Struts — Include Configuration
Constants
Constants are the framework’s settings. The documentation names two roles: overriding a setting such as the maximum upload
size or devMode, and choosing which implementation of a bean type the framework uses. A constant is always a string
name and a string value; the name is conventionally struts. plus a dotted path, and Struts exposes every built-in name as
a field of the StrutsConstants class.
| Place | Syntax | Notes |
|---|---|---|
|
|
The recommended place: typed, validated by the DTD, versioned with the application |
|
the same |
Lets a plugin set defaults; |
|
|
Legacy file kept "for backward-compatibility with WebWork"; overrides XML (struts.properties, the Legacy File) |
|
|
Every init-param becomes a constant; overrides everything above |
a custom |
Java code |
For configuration computed at start-up |
<!-- struts.xml -->
<struts>
<constant name="struts.devMode" value="true"/>
</struts>
<!-- web.xml -->
<filter>
<filter-name>struts2</filter-name>
<filter-class>org.apache.struts2.dispatcher.filter.StrutsPrepareAndExecuteFilter</filter-class>
<init-param>
<param-name>struts.devMode</param-name>
<param-value>true</param-value>
</init-param>
</filter>
# struts.properties
struts.devMode=true
A constant whose name is misspelled is accepted without a message: adding <constant name="struts.devmod" value="true"/>
to the Bookshelf’s struts.xml reloaded cleanly, the application kept answering and devMode stayed false (observed).
The framework looks constants up by name and nobody looks up struts.devmod.
Value Substitution
Since Struts 2.5.6 a constant value in struts.xml can read a Java system property or an environment variable, with an
optional default after a colon. The substitution applies only to constants, and only to those two sources.
<struts>
<constant name="os" value="Current OS = ${os.name}"/>
<constant name="struts.devMode" value="${env.STRUTS_DEV_MODE:false}"/>
</struts>
The Bookshelf uses the second form, so a developer switches devMode on with STRUTS_DEV_MODE=true in the environment of
the JVM and production keeps the safe default. The substitution is done by EnvsValueSubstitutor, and calling it directly
on the Struts 7.4.0 class path gave these results.
$ java -cp ... Subst
false # ${env.STRUTS_DEV_MODE:false}, variable not set
Current user = airurueta # ${user.name:nobody}
${env.BOOKSHELF_MISSING} # no variable, no default: left as written
$ STRUTS_DEV_MODE=true java -cp ... Subst
true
Two details matter. A variable with no default and no value is not an error; the literal text stays in the constant, which
for a boolean constant is then false. And ${...} in a <constant> is not OGNL: the same characters in a result
parameter such as <param name="location">${target}</param> are evaluated against the value stack (see Results & Result Types).
The Constants Worth Knowing
default.properties in the 7.4.0 core JAR has about 75 active entries. These are the ones an application normally
touches or must not weaken, with the 7.4.0 default (checked in the JAR). The security rows are the secure defaults: change
them only for a stated reason.
| Constant | Default | Effect |
|---|---|---|
|
|
Development mode: reloads resource bundles and XML on change, turns ignorable problems into errors, shows debug output. Never in production (Reloading and Development Mode) |
|
|
Extensions the |
|
|
The |
|
|
The |
|
|
Namespace resolution: use everything before the last slash (see Packages and Namespaces) |
|
|
Whether an unmatched namespace falls back to the empty namespace |
|
|
Encoding for request parameters and resource bundles |
|
not set |
A fixed locale; if unset the request locale is used |
|
|
Restricts request-derived locales to those the JVM knows |
|
not set |
Extra global resource bundles, comma separated (see Localization and i18n) |
|
|
The multipart parser ( |
|
|
Largest accepted request, in bytes (2 MiB) |
|
|
Limits on files and on non-file fields in a multipart request |
|
|
Longest string value in a multipart request |
|
empty |
Temporary directory; empty means the servlet container’s temporary directory |
|
|
Default theme of the UI tags (see Themes & Templates) |
|
|
Template engine of the UI tags |
|
|
Serve the framework’s own JavaScript and CSS from the JAR |
|
|
URL prefix of that static content |
|
|
Send caching headers for it |
|
|
The OGNL allowlist; "strongly recommended to keep enabled" |
|
|
Only members annotated with |
|
|
Drops the annotation requirement on simple setters while migrating; "set to false as soon as possible" |
|
|
Static fields in OGNL expressions |
|
|
Only the standard map classes can be created from OGNL |
|
|
Maximum length of an OGNL expression; see What Changed Recently for the guide that says 150 |
|
|
Block OGNL access to proxy objects and to classes in the default package |
|
|
The |
|
|
Throw when a property is missing in an expression |
|
not set ( |
Reload XML configuration when a file changes (Reloading and Development Mode) |
|
not set ( |
Reload resource bundles on each request |
|
not set |
Which |
|
not set |
Additional properties files to load, comma separated |
|
|
Cache implementations for parsed expressions, localized text and proxy detection (sizes: |
|
|
Template performance settings; |
|
|
How the URL tags build absolute URLs |
|
|
The |
|
|
Whether the |
|
|
Prefix of the hidden field the |
|
|
Largest CSP violation report accepted |
|
|
Serve WebJar assets under the static path; |
The constants are read when the container is built, so changing one means changing the configuration and reloading it. The
complete list is the file itself, and StrutsConstants documents each name in Javadoc.
Constants That Choose a Bean
The second role of a constant is selection. When several beans of one type are registered under different names, a
constant holds the name of the one to use. The comment in default.properties shows the idea with struts.objectFactory, whose short name spring selects the Spring
plugin’s object factory (see Spring Integration); in Struts
7.4.0 struts.date.formatter, struts.url.encoder and struts.proxyService work the same way, and their defaults in the
table above are bean names (dateTimeFormatter, strutsUrlEncoder, struts). A bean registered under the name struts
is the framework’s default for its type (Bean Configuration).
default.properties and struts.properties
struts.properties, the Legacy File
struts.properties is a plain properties file on the class path (normally WEB-INF/classes), one constant per line. The
constant documentation keeps it "for backward-compatibility with WebWork", and it is loaded by the 7.4.0 Dispatcher
(step 4 of Load Order: Who Wins); in the Bookshelf run it overrode struts.xml and was overridden by a filter init-param.
bookshelf.order=struts.properties
bookshelf.pageSize=50
Prefer <constant> in XML for new code: the DTD checks the structure, the file is versioned with the rest of the
configuration, and the load order is easier to reason about. The books of the Struts 2.0 era configure almost everything
here (Where the Books Are Outdated), and every setting in this form maps one to one onto a <constant>. Struts also reads extra
properties files named by struts.custom.properties, and extra resource bundles named by struts.custom.i18n.resources.
Source: Struts — Constant Configuration
default.properties: Not an Application File
The documentation page default.properties says that "to change any of these properties, specify the property key and value
in an default.properties file", located anywhere on the class path, and the development-mode page says devMode "can be set
to true or false in default.properties". That does not match 7.4.0. The framework reads exactly one resource of that name,
org/apache/struts2/default.properties inside struts2-core.jar, as step 1 of the load order, and an application file
called default.properties is never read. In the Bookshelf, a WEB-INF/classes/default.properties with
bookshelf.pageSize=99 changed nothing, while the same line in struts.properties took effect (row four and five of the
table in Load Order: Who Wins, observed). Read default.properties as the reference list of names and defaults, and override in
struts.xml, struts.properties or an init-param.
Source: Struts — default.properties
struts-default.xml
The framework’s own configuration is the file struts-default.xml in the core JAR. It is not copied into the application:
it is loaded first by the Dispatcher (step 2) and it supplies
-
<include file="struts-excluded-classes.xml"/>, which setsstruts.excludedClassesand the related excluded-package constants (the OGNL block list); -
<include file="struts-beans.xml"/>, which registers the framework beans, each under the namestruts(ObjectFactory,ResultFactory,ActionFactory,ConverterFactory,InterceptorFactory,ValidatorFactory,UnknownHandlerFactory,ActionProxyFactory, theFileManagerand its factory) and a few others; -
one abstract package,
struts-default, holding the<result-types>(chain,dispatcheras the default,freemarker,httpheader,redirect,redirectAction,stream,plainText,postback), every named<interceptor>, the interceptor stacks (defaultStackamong them) and the default interceptor reference.
Your packages extends="struts-default" to inherit all of that. The page that shows the whole file also says how to leave
it out: "see the struts.configuration.files setting in default.properties". That name does not exist in 7.4.0: it is
not in default.properties, not in StrutsConstants, and not read by the Dispatcher. The working mechanism is the
config filter init-param, a comma-separated list of XML files that replaces the default list
struts-default.xml,struts-plugin.xml,struts.xml.
<filter>
<filter-name>struts2</filter-name>
<filter-class>org.apache.struts2.dispatcher.filter.StrutsPrepareAndExecuteFilter</filter-class>
<init-param>
<param-name>config</param-name>
<param-value>struts-default.xml,struts.xml</param-value>
</init-param>
</filter>
With that list the Bookshelf’s /books answered 200 and the action of the plugin JAR (/plugin/ping, which answered 204
before) answered 404, because struts-plugin.xml was no longer in the list; with only struts-default.xml in the list,
/books answered 404 too (observed). A file whose name does not end in .xml is rejected with Invalid configuration
file name. struts-deferred.xml is always loaded afterwards.
Source: Struts — struts-default.xml
Bean Configuration
Struts has its own small dependency injection container (the documentation compares it with Google Guice). It builds the
key framework objects, which is how any part of the framework can be replaced, extended or removed, and how plugins add
Spring or SiteMesh support. Most applications never touch it, but <bean> is also the way to register your own service so
the framework can inject it into actions (see Dependency Injection and CDI).
| Attribute | Required | Description |
|---|---|---|
|
yes |
The class to instantiate or manipulate |
|
no |
The primary interface the class implements; the container selects among beans of one type by |
|
no |
A name unique among beans of the same |
|
no |
|
|
no |
Inject static methods and fields instead of creating instances (do not combine with |
|
no |
Whether the bean is optional |
A bean is used in one of two ways: created by the container and injected into other objects (usually with type), or, with
static="true", only having values injected into its static members so that objects the container does not create can
still receive constants.
The Bookshelf registers two implementations of a catalogue interface and picks one by name.
package org.example.bookshelf;
import java.util.List;
public interface Catalog {
List<String> titles();
}
public class InMemoryCatalog implements Catalog {
public List<String> titles() { return List.of("Neuromancer", "Dune"); }
}
public class SortedCatalog implements Catalog {
public List<String> titles() {
return new InMemoryCatalog().titles().stream().sorted().toList();
}
}
<bean type="org.example.bookshelf.Catalog" name="inMemory" class="org.example.bookshelf.InMemoryCatalog"/>
<bean type="org.example.bookshelf.Catalog" name="sorted" class="org.example.bookshelf.SortedCatalog"/>
ConfigAction (above) asks for the sorted one with @Inject("sorted"), and the response confirms the choice.
$ curl -s http://localhost:18080/g8conf/config
devMode=false
pageSize=50
order=struts.properties
titles=[Dune, Neuromancer]
titles is sorted, so the container injected SortedCatalog, and the other values are the effective constants of the
fourth step of Load Order: Who Wins (this run used the WAR with its struts.properties).
The injected field can be private. A constructor with parameters is not supported for an action: the first attempt to
inject the same four values through an @Inject constructor made the whole application fail to start with Unable to load
configuration and Caused by: Action class [org.example.bookshelf.ConfigAction] does not have a public no-arg constructor
(observed); every URL of the context then answered 404.
The documentation’s own bean example registers com.opensymphony.xwork2.ObjectFactory. In Struts 7 the package was
renamed (the 6 to 7 migration guide: "the com.opensymphony.xwork2 package has been renamed/migrated to
org.apache.struts2`"), and in 7.4.0 the type is `org.apache.struts2.ObjectFactory; use the org.apache.struts2 names in
every type= attribute.
Source: Struts — Bean Configuration
Source: Struts — Dependency Injection
Source: Struts 7 Javadoc — @Inject
Unknown Handlers
An UnknownHandler is a bean the framework calls when an action, a result or a method is not found. Several can be stacked
with <unknown-handler-stack>, which must be the last child of <struts>, and they are tried in the order listed until one
handles the request. The default manager is DefaultUnknownHandlerManager, replaceable with the constant
struts.unknownHandlerManager. The documentation page still shows the old com.opensymphony.xwork2.UnknownHandler name;
in 7.4.0 it is org.apache.struts2.UnknownHandler.
public class ShelfUnknownHandler implements UnknownHandler {
@Override
public ActionConfig handleUnknownAction(String namespace, String actionName) {
if (!"/".equals(namespace) || !"bestsellers".equals(actionName)) {
return null; // not ours: let the next handler, or the 404, take over
}
return new ActionConfig.Builder("default", actionName, BookAction.class.getName())
.addResultConfig(new ResultConfig.Builder("success",
"org.apache.struts2.result.ServletDispatcherResult")
.addParam("location", "/WEB-INF/views/books.jsp").build())
.build();
}
@Override
public Result handleUnknownResult(ActionContext ctx, String actionName, ActionConfig cfg, String code) {
return null;
}
@Override
public Object handleUnknownActionMethod(Object action, String methodName) {
return null;
}
}
$ curl -s -o /dev/null -w "%{http_code}\n" http://localhost:18080/g8conf/bestsellers
200
$ curl -s -o /dev/null -w "%{http_code}\n" http://localhost:18080/g8conf/nothere
404
The 404 body names the problem exactly: There is no Action mapped for namespace [/] and action name [nothere] associated
with context path [/g8conf]. For a simple fallback page, a default-action-ref in the package is the lighter tool (see
Packages and Namespaces); an unknown handler is for logic, such as mapping legacy URLs.
Source: Struts — Unknown Handlers
Plugin Configuration: struts-plugin.xml and struts-deferred.xml
A plugin is a JAR on the class path. If it contains struts-plugin.xml at its root, Struts loads it with the same parser
and format as struts.xml, before struts.xml, so a plugin can add packages, results, interceptors and actions, override
constants and register beans. A struts-deferred.xml is loaded after everything else, which is where a plugin declares an
extension point that must see the final configuration. Because struts.xml comes later, the application always has the last
word over the plugin’s constants.
<struts>
<constant name="bookshelf.order" value="plugin"/>
<constant name="bookshelf.pageSize" value="5"/>
<package name="bookshelf-plugin" extends="struts-default" namespace="/plugin">
<action name="ping">
<result type="httpheader"><param name="status">204</param></result>
</action>
</package>
</struts>
Put into WEB-INF/lib/bookshelf-plugin.jar, this file added the /plugin/ping action with no change to the application’s
struts.xml (curl answered 204), and its constants lost to the application’s (first row of the table in Load Order: Who Wins).
The same mechanism is what the Convention, JSON, Spring, Tiles and other official plugins use; the developer guide to
writing a plugin, extension points and the plugin registry is the "Plugins Architecture" page.
Source: Struts — Plugins Architecture
Reloading and Development Mode
Two constants decide whether configuration changes need a restart.
| Constant | Effect |
|---|---|
|
Re-reads the XML configuration (and validation files) when a file the framework tracks changes; "not recommended for production use" |
|
Reloads resource bundles on every request |
|
Turns both of the above on if they are not set explicitly, and raises ignorable problems (an unknown request parameter, for instance) to errors |
The development-mode page lists the same three effects, and warns that the option "can expose sensitive data of your
application". In 7.4.0 the StrutsBeanSelectionProvider does the switching: when devMode is true it sets
struts.i18n.reload, struts.configuration.xml.reload and a zero FreeMarker cache delay only if the application has not
set them. The reload is conditional: the ConfigurationManager asks every provider needsReload() and rebuilds only when a
tracked file changed, so the cost is a file-timestamp check per request, not a rebuild.
The Bookshelf sets struts.configuration.xml.reload to true explicitly, and the reload behaviour was verified in the
exploded WAR without restarting Tomcat.
$ curl -s -o /dev/null -w "%{http_code}\n" http://localhost:18080/g8conf/admin/export
404
$ # add <action name="export" .../> to WEB-INF/classes/bookshelf/struts-admin.xml
$ curl -s -o /dev/null -w "%{http_code}\n" http://localhost:18080/g8conf/admin/export
200
The same run showed three limits.
-
A new file matching the include pattern is not picked up until a tracked file changes (Include Configuration).
-
A reload with an error in a file does not give a clear page. With
struts-defualtin an included package, the request failed withHTTP 500and the unhelpfulNoSuchMethodException: ...BookAction.execute()rather than the configuration error, and the application recovered as soon as the typo was fixed (observed). The clear message appears when the application starts:Dispatcher initialization failedwithCaused by: Parent package is not defined: struts-defualt, and every URL of the context answers 404. -
After a configuration reload, the framework’s own static files stopped being served: before the reload
GET /g8conf/static/utils.jsanswered 200, after touchingstruts.xmlit answered 500 withCannot invoke "java.util.List.iterator()" because "this.pathPrefixes" is nulluntil the application was redeployed (observed, Struts 7.4.0 withstruts.configuration.xml.reload=true). Treat reload as a development aid and test static assets after it.
Production: leave both reload constants off, devMode false. The ${env.STRUTS_DEV_MODE:false} form of
Value Substitution keeps that safe by default.
Source: Struts — Development Mode
Source: Struts — Reloading configuration
The Filter and Its Init-Params in web.xml
The documentation names two ways to enable Struts: the single StrutsPrepareAndExecuteFilter, or the pair
StrutsPrepareFilter and StrutsExecuteFilter when another filter (SiteMesh) has to sit between the prepare and execute
phases. Both live in org.apache.struts2.dispatcher.filter. The Bookshelf’s web.xml is the Servlet 6.1 form (version="6.1",
https://jakarta.ee/xml/ns/jakartaee).
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="https://jakarta.ee/xml/ns/jakartaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="https://jakarta.ee/xml/ns/jakartaee https://jakarta.ee/xml/ns/jakartaee/web-app_6_1.xsd"
version="6.1">
<filter>
<filter-name>struts2</filter-name>
<filter-class>org.apache.struts2.dispatcher.filter.StrutsPrepareAndExecuteFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>struts2</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
</web-app>
Source: Struts — web.xml
The init-params the 7.4.0 Dispatcher reads, besides every constant, are these.
| Init-param | Meaning |
|---|---|
|
Comma-separated XML files to load instead of |
|
Comma-separated |
|
Comma-separated |
|
Extra packages searched for static content |
|
A custom |
Mapping the filter to /* is "the preferred way". With a narrower mapping such as *.action the filter no longer sees
the requests for its static files and a second <filter-mapping> for /static/* is needed, and the constant
struts.action.excludePattern (a comma-separated list of regular expressions, for example .*unfiltered.*,.*\.nofilter)
makes the filter pass chosen URLs through untouched. A <taglib> entry in web.xml for the Struts tags is "neither
required nor recommended": the TLD in the core JAR is discovered by the container.
Static content has a path mismatch in the documentation. The Configuration Files page says requests that start with
/struts/ are served from org.apache.struts2.static and template; in 7.4.0 the prefix is struts.ui.staticContentPath,
default /static, and GET /g8conf/struts/utils.js answered 404 while /g8conf/static/utils.js answered 200 (observed).
Source: Struts — Static Content
From struts-config.xml to struts.xml
Applications and books of the Struts 1 era configure with struts-config.xml, a *.do servlet mapping and a plug-in
element. The elements have direct, simpler equivalents in Struts 7. Struts 1 reached end of life on 2013-04-05 (see
introduction and history), and its documentation is no longer online.
Struts 1 (struts-config.xml, web.xml) |
Struts 7 |
|---|---|
|
One filter on |
|
|
|
No form class: properties on the action, or a model object with |
|
|
|
|
|
Constants and interceptors |
|
Resource bundles; |
|
A plugin JAR with |
|
A container-managed |
Modules and |
Packages and namespaces (see Packages and Namespaces) |
Struts Blank application |
No archetype for Struts 7: the maven archetypes stop at 6.0.0 (see Struts Getting Started) |
The two configuration systems share nothing at run time: Struts 1 is not part of Struts 7, and the migration guides are the reference.
Pitfalls
| Symptom or error | Cause | Fix |
|---|---|---|
The whole context answers 404 and the log shows |
A configuration error at start-up: |
Read the first |
|
An action class with only a constructor with parameters |
Give the action a public no-arg constructor and inject with fields or setters |
A value in |
The same constant is set in |
Search the application for the name; keep each constant in one place |
|
Only the core JAR’s copy is read |
Use |
|
Constants are matched by name; an unknown name is accepted |
Copy names from |
|
A missing included file is skipped, logged at debug level only |
Request an action of every module in a smoke test |
A new file matching an include pattern is not loaded after a reload (observed) |
The reload is triggered by a change in an already tracked file |
Touch a tracked file, or restart |
|
The reload failed because of an error in the edited file |
Fix the file; check the log for the real configuration error |
Struts static files ( |
Reload in 7.4.0 left the static content loader without its path prefixes |
Dev only: redeploy; keep reload off in production |
|
Elements in the wrong order for the DTD ( |
Follow the DTD content model; keep the |
|
|
Link without an extension or with |
|
The constant was left |
|
The OGNL, allowlist or annotation constants were switched off "to make it work" |
Migration from an older application |
Fix the cause (annotate the setter, add the class to the allowlist); keep the secure defaults of The Constants Worth Knowing |
|
Stale documentation (struts-default.xml, The Filter and Its Init-Params in web.xml) |
Use the |
What Changed Recently
| Version | Change |
|---|---|
2.5.6 |
|
6.0 |
The Velocity support moved out of the core into a plugin; the DTD is |
7.0 |
The |
7.2.0 and later |
Performance constants ( |
7.4.0 |
Present in |
The 6 to 7 migration guide says the OGNL limit "has been reduced from 200 to 150", but the 7.4.0 default.properties has
struts.ognl.expressionMaxLength=256 and the security guide also says 256; this page uses 256 because that is what the
JAR contains.
Source: Struts — Version Notes 7.4.0
Where the Books Are Outdated
Struts in Action (Manning, 2002) configures Struts 1 with struts-config.xml; its chapters on first application and
configuration are covered by the table in From struts-config.xml to struts.xml. Struts 2 in Action (Manning, 2008, both listed in the section
bibliography) is the Struts 2.0 book and its chapters on plugins and configuration still describe the right ideas and the
wrong details.
| The book’s era | Today (Struts 7.4) |
|---|---|
|
|
|
|
A DOCTYPE for DTD 2.0 or 2.1 |
The 6.0 DTD (struts.xml); there is no 7.0 DTD |
|
|
The Struts 2 plugin chapter’s list (SiteMesh, Tiles, JFreeChart, and others) |
17 plugins are published at 7.4.0; several of the old ones are deprecated or removed (see Other Plugins) |
Settings for features that were later removed ( |
Check every name in |
A loose default for every security-sensitive constant |
Secure defaults in 7.x (The Constants Worth Knowing) |
Reloading configuration "just by editing" |
|
The official pages need the same care: the pages quoted above still show com.opensymphony.xwork2 names, the Java EE 3.1
namespace in the web.xml examples, struts.configuration.files, the /struts/ static prefix and the "default.properties
in your application" advice, and none of those work as written in 7.4.0.