It turns out we had some customizations going on in our stack that were throwing a wrench into the gears. This was in some older common code that someone in our organization wrote years ago.
The base action class that all of my actions extend was a class that extended com.opensymphony.xwork2.ActionSupport and implemented the com.opensymphony.xwork2.interceptor.ParameterNameAware interface which means it had an acceptableParameterName method which looked like this:
public boolean acceptableParameterName(String parameterName) {
return prepareInvoked || getPreprepareParams().contains(parameterName);
}
That getPrepareaParams method returned a list that had as its only entry "id" and the prepareInvoked gets set to true when prepare() is called for the first time.
Our stack calls the params interceptor twice. The first time through it only processed the "id" parameter. The second time through, the acceptableParameterName method always returned true. I'm not sure what the intended functionality of this was. My best guess is that it was an attempt to optimize something and was probably targeted at some other application that is using this common code.
But the unintended side effect of this method after the upgrade was that the default excludeParams as well as the explicit excludeParams setting in my struts.xml were both ignored.
I have removed this method from the base class and things seem to be working. There is now a single DEBUG line from the params interceptor:
DEBUG [ com.opensymphony.xwork2.interceptor.ParametersInterceptor ]: Parameter [action:PersonalInfo_next] didn't match acceptedPattern pattern!
Compared to the 300+ lines of warning I was getting before, this is downright acceptable!