I think the ideal way for builds to work is to be able to define packages (that can be source, libraries, data, a genrule that runs a shell command) and the dependencies of that package (references to other packages). The build system should just know how to build various kinds of code (or maybe have one spot for configuring how your Java build runs, for example). It should also know what output files to look for, so that it can avoid re-building files over and over again. For the common languages, it can deduce where the built file should be. For genrules, the developer can specify what the output files are.
Issues I had with ant: Ant requires you to specify the mechanics for how to build everything. It is difficult to impossible to express more complicated build rules (e.g. arbitrary shell commands were a huge pain). It also was very slow for incremental builds involving multiple projects, and it did not provide incremental builds for most build artifacts. For example, if an 'ant' rule generates a file, it will do so every time unless you write custom code to skip doing that based on hash or timestamp.
Ant is fine for one blog of code, but when you want to stop building the world, I find things get very difficult. It became painfully slow to build stuff, and difficult to write build files correctly as our system grew.
I don't have a ton of experience with other systems, but they seem to share a lot of the same problems.
One of the closest ones I saw was gyp (http://code.google.com/p/gyp/), but it didn't support Java