For different reasons, Ant and Maven are both terrific and terrible at once. Ant allows you to describe your build however you please, but you must effectively do it from scratch for each project. Maven encourages many best practices by providing a lot of build features out-of-the-box, adopting a sort of "convention over configuration" philosophy popularized by the Ruby on Rails project, among others.
So for my money, Maven is the logical choice for a typical Java project. Unfortunately, few Java projects are typical, especially few enterprise Java projects. And for those projects, Maven can be frustrating because, like a child or a turtle, it's difficult to make it do something it doesn't want to do.
For example, let's say I'm debugging a build and I want to echo the value of a property to the console during a build. With Ant, I could add a target to build.xml:
<project>
<target name="debug">
<echo>The value is ${some.property}</echo>
</target>
</project>
With Maven 1, I can invoke Ant tasks within a goal defined in maven.xml:
<project>
<goal name="debug">
<echo>The value is ${some.property}</echo>
</goal>
</project>
Maven 1 provides easy access to Ant tasks, as you can see. Unfortunately, doing anything non-trivial with Maven 1 involves Jelly, which is pure "executable XML hell". Maven 2 did away with Jelly, but...
With Maven 2, it's bewilderingly complicated to echo a simple property to the console. You must include the "antrun" plugin in your pom.xml, configure an execution to run Ant's echo task, and bind that execution to a particular phase of your build (but see comments for other options). Ready? Here we go:
<project>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-antrun-plugin</artifactId>
<executions>
<execution>
<id>debug</id>
<phase>validate</phase>
<configuration>
<tasks>
<echo>The value is ${some.property}</echo>
</tasks>
</configuration>
<goals>
<goal>run</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>
Note that our choice of the "validate" phase is completely arbitrary, but in order for our task to execute, it must be bound to some phase. Unlike our Ant and Maven 1 examples above, Maven 2 provides no way to invoke a single task without invoking all other tasks bound to the same phase along with all those before it. Maven 2 introduced the concept of profiles to address this perceived limitation, but I'll save that discussion for a future blog post.
Clearly, the Maven designers have violated a fundamental tenet: "the solution cannot be more abstract than the problem!" In trying to define and encourage best practices, Maven unapologetically sacrifices simplicity and flexibility, arguably two of the most important qualities of any software build tool, even for those projects exhibiting all accepted best practices.
Somewhat surprisingly, I generally still think it's worthwhile to convert your Ant projects to Maven, no matter how difficult. Maven's pain is concentrated, sort of like filling a cavity early in the project, resulting in a relatively pain-free existence once your project leaves the dentist's office. Ant is more like a minor toothache annoying you for the entire duration of your project, and then spreading to the other projects in your mouth as each begins its life with a copy of some other's build.xml file. Yes, of course it's simpler to do simple things with Ant, but adding enough of those simple things to projects over time can (and will, in my experience) lead to a pretty darn complex, brittle, and incomprehensible build.
In the long run, I think you'll end up hating Maven less than Ant, too. But if you insist on using Ant, use Ivy to manage your dependencies.
UPDATED Maven 2 example, removing unnecessary <property> element to reflect Eric's comment.