A cow orker and I once tried to work out just how many different languages you had to know to understand all of a J2EE project. Between programming languages, markup and templating languages and a thousand different flavours of configuration files (each a language in its own right), the assumed knowledge for a pretty simple project was pretty daunting.
You don't see nearly so much of this with other languages. Not just the proliferation of different languages, but also the proliferation of configuration files. Everything that's optional or that needs to be easily changed (scripted) gets dragged out into something that isn't Java code, where in other languages it would much more likely be written in one language, and programatically configured.
A configuration/scripting file makes sense when it's one component and one config file. But when you start gathering components together, you end up with one or more configuration files per component, and things start to get brittle and fiddly: especially if you want to make regular switches from one configuration mode to another (for example testing and production), in a way that crosses a number of different files.
It's tempting to say that this is because of a flaw in Java: there's something wrong with the language that makes people want to get away from it. This could partially be true, certainly I'd love to switch regularly to a dynamically typed language with closures1, but it'd be lazy to say it's the real answer. For all Java's flaws, it's still a relatively clear and flexible language. A little verbose, perhaps, but not that much. And certainly not compared to, say, anything written in XML.
As far as I can tell, there's just a firm cultural belief amongst Java developers, perhaps influenced by its C ancestry, that anything written in Java code is inviolate: set in stone. If there's anything that could possibly ever change, it should be immediately externalised. If there's a process that might need to be configured, it will need to be done in its own scripting language outside the Java code. Programmatic configuration is bad, because it's putting something that might change inside the code.
This is rubbish. Java is a nice readable and writeable language. It's been designed to have small, independent compilation units so the compile cycle is short. It doesn't suffer from the "fragile base class" problem. Changing a Java class really isn't that big a deal most of the time.
But we feel the Java code must remain inviolate, and anything that might change must live outside, where it's safe to put volatile things.
Of course we don't expect end-users of an application to configure it by changing the code and recompiling. And anything you want to change during deployment needs to be external as well. But really, with the number of configuration files that a Java server-side app accumulates, maybe one tenth of it is actually stuff you'll eventually want exposed to the end-user. And rather than spread that tenth across a bunch of different configuration files, you're much better off if you're able to write some kind of code that centralises it in a way that makes sense for your application, rather than for the various libraries it's making use of.
Update: Daniel Sheppard contributed some nice tips on the subject.
1 ...because I know someone will at least be thinking this. Anonymous inner classes are not closures. They do a reasonable (if ridiculously verbose) job of faking it, but that's all.