Final Interfaces in Java
“W…what?” I hear you say. Java can’t have final interfaces! Sometimes however, it is convenient to expose an interface, but assert that API users not implement it. This is the case when multiple-inheritance may be needed, but you don’t want to break people’s code when adding new methods. This issue came up when designing gRPC stubs in Java. In this post, I’ll show a neat trick to work around it.
Stubs and RPCs
In gRPC Java, we provide tools to automatically generate code from Protobufs. The generated code includes all the nice type-safety for sending RPCs, and reduces the boilerplate needed to get up and running. The code generated is called a Stub. Here is a sample Protobuf service:
service MyService {
rpc MyMethod(MyRequest) returns (MyResponse);
}
Originally, this would be turned into code that looked like this:
package mypackage.proto;
public final class MyService {
public interface BlockingMyService {
MyResponse myMethod(MyRequest req);
}
public static abstract class MyServiceBlockingImpl
extends AbstractStub implements BlockingMyService {
@Override
public MyResponse myMethod(MyRequest req) {
throw new UnsupportedOperationException();
}
}
}
Pretty straightforward code. An interface and a implementation are created, allowing users to use either one based on their own preference.
API Evolution and Interfaces
The problem here, is that Protobufs are designed to be modifiable. It’s okay to add new methods, add new fields, and evolve the API over time. Java interfaces, on the other hand, are very hard to modify. Adding another method to a Java interface will break every class that extends it. While it may be possible to update all of the extenders in one commit, it isn’t always feasible or easy. What’s worse, if interfaces are exposed on the public API surface, it may be impossible to ever modify them again. I hope you are using Semver and can bump the major version!
API Evolution and Abstract Classes
The gRPC team noticed this early on fortunately, and the interfaces were removed. Instead, the abstract classes are the correct way to to use stubs. Classes provide a way to update the service definition without breaking ABI / API compatibility. When a new RPC is added, a method is generated that throws an exception. Since no current child classes know about the method, there is no danger of accidentally calling it. Life is good and API evolution can continue.*
“Final” Interfaces
One thing we give up be moving to abstract classes is the perks of interfaces. One of the things interfaces provide is compatibility with reflection Proxies. Proxies allow doing cool things, like intercepting calls, tracing, and customized class loading. Wouldn’t it be interesting if, somehow, the interface could only be implemented by the a single abstract class, which all base classes MUST extend?
The pattern of interface+base implementation has existed for a long time, but in Java it never really had any teeth. The best we could do is add a dontImplementThisInterfaceOrWeWillBreakYou()
method so that stray implementers would be warned to use the base class instead. Rather than depend on humans to honor the documentation, how about we make the compiler do it?
Without further ado, here is the trick:
package mypackage.proto;
public final class MyService {
public interface BlockingMyService {
MyResponse myMethod(MyRequest req);
void dontImplementMe(Private1 p);
void dontImplementMe(Private2 p);
}
private interface Private1 {}
private interface Private2 {}
public static abstract class MyServiceBlockingImpl
extends AbstractStub implements BlockingMyService {
@Override
public final void dontImplementMe(Private1 p) {
throw new AssertionError();
}
@Override
public final void dontImplementMe(Private2 p) {
throw new AssertionError();
}
// ...
}
}
A couple of interesting things are going on here. First, private classes, fields, and methods that are defined within the same file ignore visibility modifiers. This allows the BlockingMyService
interface to reference the private types. This in turn prevents any other class outside of this file from implementing it. However, MyServiceBlockingImpl
can extend it, being the only class that can legally do so.
The second interesting thing is that there are two private types instead of just one. The issue here is that all interface methods are public, which means they must be public on all subclasses. While callers may not be able to call dontImplementMe()
with an instance of a Private1
, they can call it as dontImplementMe(null)
. While this code tries to prevent that by throwing an error, that doesn’t stop users from referring to the “private” methods. Instead, by using two private types, the compiler cannot disambiguate between the two and thus cannot call either with a null
.
The net of all this is an interface that anyone can refer to, and call methods on, but is unable to implement. (Reflection not withstanding).
Conclusion
While it’s probably too late to use this technique in gRPC, it’s still handy in other circumstances. If you commonly have pairs of interfaces and abstract classes in your code, you can use this pattern to stake out your API boundaries more strongly.
Notes:
* Java 8 added default methods to address similar issues, but gRPC has compatibility back to Java 6, so we can’t use ‘em.