Usando Date, é complicado mesmo, porque a API antiga não fornece mecanismos adequados para cálculos com datas.
O ideal hoje em dia é usar a nova API java.time, como já indicado acima. Esta API está disponível desde o Java 8 (março de 2014), então a menos que vc esteja mexendo em código legado que ainda depende de java.util.Date, não há motivo para continuar sofrendo 
Enfim, se quer mesmo continuar usando Date, tem que fazer tudo na mão. E como vc faz o cálculo manualmente? Basicamente:
- subtrai o ano atual do ano de nascimento
- se ainda não chegou o aniversário, subtrai 1
Algo assim:
Date dataNascimento = // alguma data de nascimento qualquer
Calendar nasc = Calendar.getInstance();
nasc.setTime(dataNascimento);
// data atual
Calendar hoje = Calendar.getInstance();
int idade = hoje.get(Calendar.YEAR) - nasc.get(Calendar.YEAR);;
int mesAtual = hoje.get(Calendar.MONTH);
int mesNasc = nasc.get(Calendar.MONTH);
if (mesNasc > mesAtual) { // se o mês do aniversário ainda não chegou
    idade--;
} else if (mesAtual == mesNasc) {
    // é o mês do aniversário, mas o dia ainda não chegou
    if (nasc.get(Calendar.DAY_OF_MONTH) > hoje.get(Calendar.DAY_OF_MONTH)) {
        idade--;
    }
}
System.out.printf("Idade=%d anos\n", idade);
Ou ainda, colocando todas as condições em um único if:
int idade = hoje.get(Calendar.YEAR) - nasc.get(Calendar.YEAR);
int mesAtual = hoje.get(Calendar.MONTH);
int mesNasc = nasc.get(Calendar.MONTH);
if ((mesNasc > mesAtual) || (mesAtual == mesNasc && nasc.get(Calendar.DAY_OF_MONTH) > hoje.get(Calendar.DAY_OF_MONTH))) {
    idade--;
}
Usando java.time
Com java.time, é muito mais simples. Mas em vez de Date, vc vai ter que usar as novas classes. No caso de data de nascimento, geralmente usa-se somente o dia, mês e ano, então a classe mais adequada é java.time.LocalDate.
Se vc só quer o valor numérico da idade em anos, basta usar um java.time.temporal.ChronoUnit:
LocalDate nascimento = LocalDate.of(2000, 2, 21);
LocalDate hoje = LocalDate.now();
long idade = ChronoUnit.YEARS.between(nascimento, hoje);
System.out.printf("Idade=%d anos\n", idade);
Se quer a idade quebrada em campos (anos, meses e dias), pode usar Period como já indicado:
LocalDate nascimento = LocalDate.of(2000, 2, 21);
LocalDate hoje = LocalDate.now();
Period idade = Period.between(nascimento, hoje);
System.out.printf("idade=%d anos, %d meses, %d dias\n", idade.getYears(), idade.getMonths(), idade.getDays());
Só um detalhe, e se alguém nasceu em 29 de fevereiro?
// nasceu em 29 de fevereiro de 2000
LocalDate nascimento = LocalDate.of(2000, 2, 29);
// em 28 de fevereiro de 2001, qual será a idade?
LocalDate hoje = LocalDate.of(2001, 2, 28);
Period idade = Period.between(nascimento, hoje);
System.out.printf("idade=%d anos, %d meses, %d dias\n", idade.getYears(), idade.getMonths(), idade.getDays());
No exemplo acima, a data de nascimento é 29 de fevereiro de 2000. Nesse caso, em 28 de fevereiro de 2001, qual será a idade?
O resultado do código acima é idade=0 anos, 11 meses, 30 dias. Isso porque a API entende que só completa-se 1 ano a partir do dia 29 de fevereiro. Mas como 2001 não é bissexto, então somente a partir de 1 de março de 2001 é que se completaria 1 ano.
Este é um corner case que precisa ser pensado caso a caso. Conheço pessoas que nasceram em 29 de fevereiro, e em anos não bissextos comemoram em 1 de março. Mas isso não quer dizer que seja uma regra universal. Aliás, nem sequer existe: aritmética de datas não tem definições precisas e oficiais como é na matemática, então cabe a vc decidir o que fazer nestes casos.