Во многих случаях эти два не имеют практической разницы, особенно если единичный экземпляр никогда не изменяется или изменяется очень медленно, например проведение конфигураций.
Я бы сказал, что самое большое отличие состоит в том, что синглтон по-прежнему является обычным Java-бином, в отличие от специализированного статического Java-класса. И из-за этого синглтон принимается во многих других ситуациях; на самом деле это стратегия реализации Spring Framework по умолчанию. Потребитель может знать, а может и не знать, что это передаваемый синглтон, он просто обращается с ним как с обычным Java-бином. Если требования изменяются, а синглтон должен стать вместо этого прототипом, как мы часто видим в Spring, это можно сделать полностью без каких-либо изменений кода для потребителя.
Кто-то еще упоминал ранее, что статический класс должен быть чисто процедурным, например java.lang.Math. На мой взгляд, такой класс никогда не должен передаваться, и они никогда не должны содержать ничего, кроме static final, в качестве атрибутов. Для всего остального используйте синглтон, так как он намного гибче и проще в обслуживании.