Po pierwsze w takich przypadkach sprawdza się, czy wyjątek który przechwytujesz nie ma swojego drzewka rodzinnego (możesz niepotrzebnie przechwytywać "rodzica" zamiast jednego z "dzieci").
Na przykład SQLException ma swoje bardziej konkretne wersje: BatchUpdateException, RowSetWarning, SerialException, SQLClientInfoException, SQLNonTransientException, SQLRecoverableException, SQLTransientException, SQLWarning, SyncFactoryException, SyncProviderException. Następnym krokiem jest przyjrzenie się wyjątkowi i jego metodom. W tym wypadku masz metodę: public int getErrorCode(), która może uszczegółowić problem lub w ogóle go rozwiązać.
Po drugie sprawdza się czy takie same jest źródło wygenerowania wyjątku - w tym wypadku sprawdzasz wynik metody getCause(), w następnej kolejności możesz porównywać wynik metody getStackTrace(), choć to jest już bardzo zależne od konkretnej wersji softu bazy danych (mimo to powinieneś wychwycić różnicę między wyjątkiem wynikającym z logina/hasła, albo z niewystarczających praw - wyniknie to z "miejsca" wygenerowania wyjątku). W ostateczności można też porównywać Porównywanie tekstu komunikatu wyjątku, to trzecie i najgorsze rozwiązanie gdy nie pozostało już nic innego.
Niestety są też takie bazy, gdzie wszystkie problemy z dostępem są zamknięte w tej samej procedurze (w efekcie dostaniesz ten sam stos na poziomie Javy), generowany jest ten sam komunikat, a przyczyna jest olewana lub inicjowana jako null (ten sam efekt).
ups. Koziołek już odpowiedział.