Aha - that old chestnut !
OK - I'm probably completely wrong but....
If you are not using JAXP, you may want to stop here.
The problem I have encountered is as follows:
Your container loads JAXP and a parser (probably crimson) as part of it's own infrastructure, then forks a classloader for your webapp.
Your webapp asks for JAXP. The classloader checks with it's parent, which is found to contain JAXP. Everyone is happy.
JAXP then asks for e.g. crimson within it's own classloader - no problem since the container already had it loaded.
Finally JAXP asks for xalan within it's own classloader - ERROR. The container's classloader has no access to a xalan jar - only the webapps classloader can see this.
If you want a quick fix, as you say, make sure the container's classloader can see xalan.
If you want a better fix, I suspect JAXP (I haven't looked) is using the current classloader, rather than it's context classloader when looking for xalan. Using the correct classloader would allow the xalan jar in WEB-INF/lib to be found - however you will still be using the container's copy of JAXP, which may expect a different version of Xalan than the one you are shipping in your webapp.
If you really want to sort out the problem for good, then I think the container needs to ensure that it's classloader does not provide any non-core (core being e.g. runtime and jsdk/jsp) classes to the containee's classloader.
With the probable advent of more JAXP-like abstractions (i.e. dynamically resoled and loaded pluggable implementations below abstract APIs) I think this problem is likely to keep biting people.
You are opening a real can of works here !
I was afraid it might be something like this. But thanks for the explanation. I think I keep the can closed and go with the cheap solution. :-)