Another call for package private visibility

I’d like to make an appeal for package-private visibility to be added to Kotlin/JVM, perhaps via an annotation.

Me and my team have been using Kotlin for several years now. Lack of package-private is probably one of the top pain points for us and I predict it will get worse in future. The problem is that the JVM does not understand internal. It’s a concept that exists only in the Kotlin language frontend. Whenever you have mismatches between language and VM concepts you can hit problems

The problems are like this:

  • Marking a constructor as internal is useless for us because we have Java users who use our API. Internal c’tors show up as ordinary public c’tors when used from Java. There is no hint anywhere whilst coding that you should not use them. So of course, there is a risk that people do. As a consequence we feel we have to preserve all c’tors from the past in our library API/ABI, which means lots of @JvmOverloads but that in turn creates ugly APIs and JavaDocs, and can be confusing because it often implies you can create an object that only really has meaning when created by our own code.
  • As is sometimes the case for Java programs, we support loading plugins (“apps”) into our main server program, and we’d like to use JVM sandboxing in future. With JVM sandboxing or Jigsaw properly activated you cannot define classes inside other packages, so you can build secure sandboxes. But Kotlin’s lack of support for package-private means that “internal” doesn’t actually mean anything to the JVM and thus can’t act as a security boundary, at least not without a lot more work.
  • We have relatively large modules because modules represent both functionality and API stability boundaries. So marking something as “internal” still exposes it to a large quantity of code that often isn’t really justified.
  • Internal works via name mangling and has a history of weird bugs caused by IntelliJ and Gradle builds not agreeing on the mangling scheme to use.

We really enjoy using Kotlin on the Corda project and there are now hundreds of app developers building apps on the platform, many of them using Kotlin too. But what we really need is the original vision of Kotlin as a better Java, rather than Kotlin as a new multi-platform language that happens to compile to bytecode in some use cases. The places that cause us the most pain for creating and evolving a large API are almost always the places where Kotlin’s semantics have deviated from Java’s in ways that cause the Java tooling to barf.

25 Likes

Not to directly disagree, I think that you make a number of very valid points. Actually better than most in the whole discussion. One thing you can do (which I’m not certain why the compiler doesn’t do) is mark your internal constructors as @JvmSynthetic which will stop Java from seeing them as part of the API. (Nothing can be done about the past though). Jigsaw’s approach to packages as security boundaries may be a sub-optimal design, but is also a fact - Kotlin should be able to play along.
A third one is the use of private classes and functions in a file that have access to each other. This allows for a narrower scope than package private, and could even be implemented on the JVM in terms of it. My code modules can also be larger and I fully see the point where internal perhaps doesn’t convey the limited access desired (in those cases where file private doesn’t work).

2 Likes

I want to have files that are not too crowded with code.
Therefor I want to have package-private as well.
I don’t like protected fictions to be package private, so maybe add a keyword local which you can add in front of protected or have it as a standalone?

The lack of package-private access is also a real problem if you’re trying to implement package by component, or trying to introduce Kotlin to a Java codebase which uses it.

2 Likes

JvmSynthetic is a neat trick, I didn’t even realise that existed. Is that an internal annotation? It’s not in an internal package but it’s also not documented.

What we can do, and I’m thinking about it, is writing our own compiler plugin or bytecode post-processor to flip the package private bits on the class files ourselves, marked by our own annotation. Basically extending the Kotlin language a bit (could call it Cortlin :slight_smile:).

There’s another possibility. Java 11 adds support for what it calls “nests” which provide more powerful control over visibility at the bytecode level. I haven’t explored the topic but I wonder if this feature can be used to better align Kotlin’s notion of internal visibility with the JVMs. For instance could a class be created that represents the entire module and then all internal classes in public packages become nested within that? I’m not sure. Probably, you still cannot do anything like internal methods on public classes.

Beware of JvmSynthetic. IDEA debugger does not step into synthetic methods.

@elizarov I hadn’t really considered that, but you’re right. Using JvmSynthetic is a hack and has resulting issues. I guess the lack of documentation is not a bad thing in this case, but it is a very powerful tool when needed. If only Java had something similar to “@Deprecated hidden” as supported by Kotlin.

We do need it

I work on Android project which already have over 20 gradle modules, most of them have over 100 files. Usual project. And of course I split logic inside modules by packages ~10 files. But I REALLY NEED to show that among that 10 files all others need only 1-2 classes. Without that every module is a mess and full of quests “what those 10 files (~2000 lines of code) purpose of”

1 Like

As a big believer in code discover-ability, isolation and intent, I would very much like - even a for show, non enforced at JVM level or whatever, package private feature.

7 Likes