“Do-er” classes versus static utility methods
Say you have the FileReader class with a read method.
I understand that class-level attributes can justify having an instance. However, what is stopping making an equivalent ReaderUtils class by pulling these same attributes inside the scope of a corresponding static read method ?
In short, what exactly justifies a "Doer" class with respect to static utility methods ?
The essence of OOP is encapsulating state/data along with associated behaviour. Static utility methods are akin to global functions in a procedural language -- you are separating behaviour (the static method) from state (the parameters to this method), thereby breaking encapsulation.
What does this mean in practice? Instead of being able to call reader.read(), you have to call ReaderUtils.read(file), which means you are now tightly coupled to the implementation -- you have made an implicit assumption that you will always use ReaderUtils and always pass in a file.
If you instead use the generic Reader interface, you can use a FileReader today but swap it out for a DatabaseReader or a HttpReader tomorrow without having to change any other code -- all the reader.read() calls will continue to work the same.
Interfaces can't be implemented statically - the implemented methods of an interface must be instance methods. As such, this prohibits injection or JNDI lookup of a "utility class" as the runtime implementation to perform some service - it must be an instance of a class. This is one of the main reasons why "doer" classes exist.
Utility classes are fine if the implementation is known at compile time, and being static methods they are more likely to naturally be stateless (just make sure any static fields are immutable/stateless), which is required by typical servers that serve requests concurrently.