Still Life with Gingerpot II
Still Life with Gingerpot II, 1912 - Piet Mondrian

There are a lot of articles and posts and Stackoverflow answers about why Java static methods are bad. Most of them are valuable and describe real problems. However, most of them are either non-essential or non-concrete, and none of them (to my knowledge)[1] have pointed out and described thoroughly the fact that they violate Dependency Inversion Principle.

There are several reasons why static methods should not be used that are commonly presented. Static methods are part of a procedural programming style that has no place in object oriented world. This is a very good reason if you are an object purist (I consider myself to be one), but if you want to convince somebody less philosophically inclined, you need to unpack the whole OO paradigm in terms of high cohesion and loose coupling, and there is nothing concrete or precise about it.

Static methods make code untestable. This is a very concrete reason. But it's not enough. Maybe you believe static methods provide a way of writing well structured reusable code very fast and using horrible tools like PowerMock to mock your static methods is a fair trade-off. Or maybe you don't believe in unit testing at all. Then this argument won't convince you.

Static methods break encapsulation. Now this is a good and precise reason not to use them, and it is good there is an article that deals with that. I am going to write about a different reason here, though.

Static methods are unsafe if they mutate global state. True. What if they don't, are they alright then? No they are not. Let me give you an example.

Example

Let's write an HTTP service endpoint which will take as query parameter a user ID, will generate a token with some claims about the user, sign it, and redirect the client to some other endpoint with the token in redirect URI. We need a RedirectUri object:

public final class RedirectUri {
    private final String targetUri;
    private final JsonNode user;
    private final PrivateKey key;
    public RedirectUri(
        String targetUri, JsonNode user, PrivateKey key
    ) {
        this.targetUri = targetUri;
        this.user = user;
        this.key = key;
    }
    public String value() throws JOSEException, JsonProcessingException {
        ObjectNode claims = new ObjectMapper().createObjectNode();
        claims.put("sub", user.findValue("id").textValue());
        claims.put("name", user.findValue("name").textValue());
        claims.put("role", user.findValue("role").textValue());
        CharSequence token = JwsUtils.signRs256( // Static method call here.
            claims, key
        );
        return new StringBuilder(128)
            .append(targetUri)
            .append("?token=").append(token.toString())
            .toString();
    }
}

Notice the JwsUtils.signRs256. This is a static method in a utilities class, but it doesn't depend on any mutable static state and it doesn't modify any static state either. It looks like this: (You can ignore the actual implementation, just keep in mind it isn't trivial.)

    public static String signRs256(
        JsonNode payload, PrivateKey key
    ) throws JOSEException, JsonProcessingException {
        JWSObject jws = new JWSObject(
            new JWSHeader.Builder(JWSAlgorithm.RS256).build(),
            new Payload(
                new ObjectMapper().writeValueAsString(payload)
            )
        );
        jws.sign(new RSASSASigner(key));
        return jws.serialize();
    }

The endpoint will find the user and will use RedirectUri this way:

return String.format(
    "redirect:%s",
    new RedirectUri(
        redirectUriBase, user, privateKey
    ).value()
);

The difficulty arises when we want to reuse RedirectUri with a different signing implementation. Perhaps we need to write another endpoint which would redirect the client with PS256 signed token instead of RS256 signed token. There is no way we can reuse RedirectUri class, because it is tightly coupled to signRs256 method. This is a violation of Dependency Inversion Principle:

High-level modules should not depend on low-level modules. Both should depend on abstractions.
Abstractions should not depend on details. Details should depend on abstractions.

RedirectUri depends on signRs256, even though the former is of a higher level than the latter. It should depend on an abstraction (i.e. interface), but a static method is one of the most concrete things there are. So let's make it non-static and see if that helps.

It's not the static part that is the problem

Let's move the static method into RedirectUri.

public final class RedirectUri {
    private final String baseUri;
    private final JsonNode user;
    private final PrivateKey key;
    public RedirectUri(
        String baseUri, JsonNode user, PrivateKey key
    ) {
        this.baseUri = baseUri;
        this.user = user;
        this.key = key;
    }
    public String value() throws JOSEException, JsonProcessingException {
        ObjectNode claims = new ObjectMapper().createObjectNode();
        claims.put("sub", user.findValue("id").textValue());
        claims.put("name", user.findValue("name").textValue());
        claims.put("role", user.findValue("role").textValue());
        CharSequence token = signRs256(claims);
        return new StringBuilder(128)
            .append(baseUri)
            .append("?token=").append(token.toString())
            .toString();
    }
    private String signRs256(
        JsonNode payload
    ) throws JOSEException, JsonProcessingException {
        JWSObject jws = new JWSObject(
            new JWSHeader.Builder(JWSAlgorithm.RS256).build(),
            new Payload(
                new ObjectMapper().writeValueAsString(payload)
            )
        );
        jws.sign(new RSASSASigner(key));
        return jws.serialize();
    }
}

There - signRs256 is a private instance method now. It even uses instance state (the PrivateKey). We don't have any static methods anymore. Does it solve anything? Well, we don't break encapsulation anymore and our code is more cohesive. But the original problem is still there - RedirectUri still can't be reused with a different signing algorithm. This is because even though signRs256 is no longer static, it is still concrete.

Fixing it with objects

Notice that even though RedirectUri is an object, the code inside it is still procedural. It consists of very concrete step-by-step instructions to first create a claims structure, then put some claims into it, then sign that structure and finally build a URI with it. This is how a computer thinks, not how a programmer should think. So let's split it into a few objects. It would have been nice if we had designed this endpoint with object thinking in mind and composed it from smaller objects from the beginning. Still, we can refactor it now. Let's use Yegor's private method rule and extract signRs256 into an object.

First we need an interface. The method creates a token, so we could write Token interface. However, a token is just a sequence of chars, and Java already has CharSequence.[2] Our token won't need any additional behaviour, it will just need to act as a CharSequence, so let's use that.

public final class Rs256Token implements CharSequence {
    private final UncheckedScalar<String> token;
    public Rs256Token(JsonNode claims, PrivateKey key) {
        token = new UncheckedScalar<>(
            new StickyScalar<>(() -> {
                try {
                    JWSObject jws = new JWSObject(
                        new JWSHeader.Builder(JWSAlgorithm.RS256).build(),
                        new Payload(
                            new ObjectMapper().writeValueAsString(claims)
                        )
                    );
                    jws.sign(new RSASSASigner(key));
                    return jws.serialize();
                } catch (JsonProcessingException | JOSEException e) {
                    throw new IllegalStateException(e);
                }
            })
        );
    }
    @Override
    public String toString() {
        return token.value();
    }
    // Irrelevant CharSequence methods omitted.
}

I have put the signing code into a lambda because executing code in constructors is bad practice and cached it in StickyScalar from Cactoos library because I don't want it to execute each time toString or another method is called.

While we're at it, let's extract User as well.

public interface User {
    JsonNode claims();
}
public final class JsonUser implements User {
    private final UncheckedScalar<JsonNode> json;
    public JsonUser(
        String id, Function<String, JsonNode> users
    ) {
        this(new UncheckedScalar<>(
            new StickyScalar<>(() -> users.apply(id))
        ));
    }
    public JsonUser(UncheckedScalar<JsonNode> json) {
        this.json = json;
    }
    @Override
    public JsonNode claims() {
        ObjectNode claims = new ObjectMapper().createObjectNode();
        claims.put("sub", json.value().findValue("id").textValue());
        claims.put("name", json.value().findValue("name").textValue());
        claims.put("role", json.value().findValue("role").textValue());
        return claims;
    }
}

users function is an abstraction of any User source (could be database, REST service or whatever), and everything else is pretty straightforward. Now RedirectUri looks like this:

public final class RedirectUri {
    private final String targetUri;
    private final CharSequence token;
    public RedirectUri(
        String targetUri, CharSequence token
    ) {
        this.targetUri = baseUri;
        this.token = token;
    }
    public String value() {
        return new StringBuilder(128)
            .append(targetUri)
            .append("?token=").append(token.toString())
            .toString();
    }
}

Notice that it no longer depends on concrete method, static or non-static. It depends on CharSequence interface now. It no longer violates Dependendency Inversion Principle and if we want to write a new endpoint to redirect with a differently signed token, we just create a new token implementation and reuse RedirectUri like this:

return String.format(
    "redirect:%s",
    new RedirectUri(
        redirectUriBase,
        new Ps256Token(
            new JsonUser(
                id, users
            ).claims(),
            key
        )
    )
);

Dependence on static methods violates Dependency Inversion principle. But it's not the "static" part that is the problem. It's the "method" part.

Still, a few questions remain

First question which should arise after reading this - so do all methods violate DI? The answer is no. First we should clarify that it's not methods themselves that violate DI, its dependence on those methods. Second, dependence on abstractions does not violate it, so dependence on interface methods is fine. Third, I would argue that dependence on details small enough such that reusing the class with different details would not make sense does not violate DI either. For example, all classes depend on primitives or Strings or even operators. If dependence on them violated DI, then all classes would be violating DI, and then it would be a useless concept. So small methods don't violate it if they are small enough to make reusing the class with different ones pointless. And it doesn't matter if they are static or not (static ones would break encapsulation but that's a different topic).

Second question which should arise - does static have really nothing to do with this? In a sense, yes. It's not that they are static, it's that they are concrete. However, static methods are always concrete, while non-static ones can implement interfaces. Also, while dependence on private static methods may not violate DI if they are small enough, dependence on public static methods always violate DI because public access means they are meant to be reused.

Third question which I'm sure somebody will have (because it occurred to me, when writing this) - ok, we had an object that was tightly coupled to an algorithm, so we extracted the algorithm into another object and encapsulated it inside the original object, making them loosely coupled through an interface. That's Strategy Pattern. Actually, no. The UML diagram might look the same, but I would argue it's not a Strategy Pattern, it's just object composition, which Strategy Pattern is a special case of. For some code to implement Strategy Pattern, we must have an object which encapsulates strategies, which means algorithms in this context. RedirectUri encapsulates tokens. Tokens are sequences of characters, while algorithms are sequences of instructions. See the difference?

Summary

Dependence on most methods, whether static or non-static, violates Dependency Inversion Principle because they are not abstractions - they are concrete. Class that depends on them cannot be reused differently. Instead, it should be composed of objects which it only knows by their interfaces. Then we can build different compositions of those objects according to our needs and achieve high code reusability.


  1. If someone has read any, please give me a link in comments. ↩︎

  2. It would be great if String was an interface, unfortunately it is not. ↩︎