Utility classes used in this way are basically namespaces for what would otherwise be (pure) top-level functions.
From an architectural perspective there is no difference if you use pure top-level "global" functions or basic (*) pure static methods. Any pros or cons of one would equally apply to the other.
Static methods vs global functions
The main argument for using utility classes over global ("floating") functions is code organization, file and directory structure, and naming:
- You might already have a convention for structuring class files in directories by namespace, but you might not have a good convention for top-level functions.
- For version control (e.g. git) it might be preferable to have a separate file per function, but for other reasons it might be preferable to have them in the same file.
- Your language might have an autoload mechanism for classes, but not for functions. (I think this would mostly apply to PHP)
- You might prefer to write
import Acme:::Url; Url::parse(url)
over import function Acme:::parse_url; parse_url();
. Or you might prefer the latter.
- You should check if your language allows passing static methods and/or top-level functions as values. Perhaps some languages only allow one but not the other.
So it largely depends on the language you use, and conventions in your project, framework or software ecosystem.
(*) You could have private or protected methods in the utility class, or even use inheritance - something you cannot do with top-level functions. But most of the time this is not what you want.
Static methods/functions vs object methods
The main benefit of object methods is that you can inject the object, and later replace it with a different implementation with different behavior. Calling a static method directly works well if you don't ever need to replace it. Typically this is the case if:
- the function is pure (no side effects, not influenced by internal or external state)
- any alternative behavior would be considered as wrong, or highly strange. E.g. 1 + 1 should always be 2. There is no reason for an alternative implementation where 1 + 1 = 3.
You may also decide that the static call is "good enough for now".
And even if you start with static methods, you can make them injectable/pluggable later. Either by using function/callable values, or by having small wrapper classes with object methods that internally call the static method.