Acho que depende um tanto... Eu considero as Exceptions uma boa alternativa na hora de propagar a mensagem de erro 'pra cima', em direção a tela.
Por exemplo, você manda inserir um registro no banco. Ele vai da tela, faz algumas validações (basicamente ver se faltam campos obrigatórios e coisas desse tipo) e passa para o lado interessante da coisa. Lá, você vai fazer os tratamentos e tchururu. Se ocorrer uma Exception, usa o try catch pra tratar e tranquilo. Só que dentro do catch, depois de tratar, você poderia dar um throw new XException ou coisa do tipo, pra que as camadas 'mais de cima' possam tratar isso (nunca testei isso, essa ideia me veio na cabeça agora, então não sei se dá certo).
Não tenho nem ideia se isso é uma boa prática... É meio estranho porque você teria que tratar os erros 2 vezes, mas é uma boa alternativa pra pegar as mensagens quando chegar na tela (e não apresentar um "Impossível cadastrar registro no banco de dados." ou "Erro.").
Algo mais ou menos assim:
public class FrontEnd {
public static void main(String[] args) {
try {
new BackEnd().fazerTalCoisa();
} catch (Exception e) {
System.err.println("Opa, deu crap.");
System.err.println(e.getMessage());
e.printStackTrace();
}
}
}
public class BackEnd {
public void fazerTalCoisa() throws Exception {
try {
} catch (Exception e) {
// Tratar o erro aqui (fazer um rollback, por exemplo)
throw new Exception("Mensagem que pode aparecer na tela.");
}
}
}
Não testei, então não sei dizer se funciona.
[Editado]
Sobre essa sua outra dúvida, eu acho que depende bastante. Você pode generalizar quando só quer ver a stacktrace e ser mais específico quando existem diversos tipos de erros (pra apresentar nas telas, por exemplo).