Why does compilation of public APIs leaking internal types not fail?

313 views Asked by At

I have the follwing Java 9 module:

module com.example.a {
    exports com.example.a;
}

With an exported type:

public class Api {

    public static void foo(ImplDetail args) {}
}

And a non-exported type:

package com.example.b.internal;

public class ImplDetail {}

The exported type uses the non-exported type as a method parameter type in a public method. I'd have assumed that the compiler would reject such an inconsistent class configuration, as clients in other modules could not really invoke the foo() method as they cannot instantiate the parameter type.

To my surprise, this module is compiled successfully by javac. I can see the special case of passing null, still I'd consider such an API definition malformed and think it should not be supported, enforced by the compiler ideally.

What's the reasoning for not disallowing such case?

1

There are 1 answers

4
Stuart Marks On BEST ANSWER

Certainly using a non-exported type in an API is bad style and is quite likely to be a design error, but it's fairly clear to me that javac isn't in a position to make this be a compile-time error.

Note that it has always been possible to use a private type in a public API, going all the way back to Java 1.0.

You already noted that code outside the module can still call Api.foo(null).

There are other cases where a caller could still use this API with a non-null reference. Consider a class public class Sub extends ImplDetail in package com.example.a. This class Sub is public and is exported and so is available to code outside the module. Thus, outside code can call Api.foo(sub) using instances of Sub obtained from somewhere.

But surely, javac can tell whether there are any subtypes of ImplDetail in any exported packages, and issue a compile-time error if there aren't any? Not necessarily. Because of the possibility of separate compilation, new classes might be introduced into a module after the compilation step that includes Api. Or, for that matter, the module-info.class file could be recompiled to change the set of exported packages.

For these reasons I think it's inappropriate for javac to raise an error at the time that it compiles the Api class. Javac does have an option -Xlint:exports that will flag cases like this as a warning, however.

Something later in the build process, such as the jmod tool, or some after-the-fact module auditing tool, could also flag the use of a non-exported type being used in an exported API. I don't think anything currently does this, though.