NOTE: The examples are in C#, as that is what I am more familiar with, but the concept should apply to Java just the same.
Ignoring the debate on when it is appropriate to use Singleton objects, one primary difference that I am aware of is that a Singleton object has an instance that you can pass around.
If you use a static class, you hard-wire yourself to a particular implementation, and there's no way to alter its behavior at run-time.
Poor design using static class:
public class MyClass
{
public void SomeMethod(string filename)
{
if (File.Exists(filename))
// do something
}
}
Alternatively, you could have your constructor take in an instance of a particular interface instead. In production, you could use a Singleton implementation of that interface, but in unit tests, you can simply mock the interface and alter its behavior to satisfy your needs (making it thrown some obscure exception, for example).
public class MyClass
{
private IFileSystem m_fileSystem;
public MyClass(IFileSystem fileSystem)
{
m_fileSystem = fileSystem;
}
public void SomeMethod(string filename)
{
if (m_fileSystem.FileExists(filename))
// do something
}
}
This is not to say that static classes are ALWAYS bad, just not a great candidate for things like file systems, database connections, and other lower layer dependencies.