On the missing package private — or why Java is better than Kotlin in this regard
New developers coming to Kotlin from Java enjoy a variety of new language features, syntactic niceties and overall improvements. However, they are often surprised by a missing feature: package private.
How it is done in Java
When it comes to visibility, Java designers made a wise decision when designing the language. The default access modifier in Java is package private. This means that all classes created without additional access modifiers are visible only to the classes in the same package. The same goes for fields and methods (which might not be as good ), but in this post we will focus on access modifiers as they refer to classes/top level constructs.
The decision to make package private the default modifier in Java was a good one. Developers need to consciously decide which classes constitute a package’s external APIs. This prevents them from exposing internal implementation. Thanks to this you can divide your module into smaller structures, each of which exposes an external interface to communicate with. You could also test classes that are internal to the package, which gives you some flexibility when deciding what constitutes the unit of code you want to test.
Until recently package private was not very popular. It was hardly used and most classes had public visibility, protected being used only with fields when inheritance was needed. However, bad practices aside, recently package visibility started enjoying some attention and being appreciated for the good construct that it is .
Of course, packages are not a perfect way of encapsulating code. Package private has some limitations that are inherent to the nature of packages. Packages are hierarchical, but the parent package cannot access its children, which might seem counterintuitive. Compared to modules, the dependencies are implicit, making packages inadequate for high-level access management. Package private also used to be flawed in that it could be circumvented by just putting a class in the same package (no longer an issue, thanks to modularity). Nonetheless, it has uses at a lower level, where we can manage and easily understand dependencies and visibilities without much overhead due to the need to define modules.
… and Kotlin
Compared to Java, Kotlin is very restrictive in its defaults with sealed classes, required override modifier, etc. However, when it comes to class visibility, not only is there no package private, but also classes are public by default. There is an additional internal modifier which makes a class visible only from within the same module, but there is no package private modifier.
Also, Kotlin differs from Java in regards to default access. The default modifier used to be internal but was later changed to public . The reasoning behind this change was simply that the public modifier was more popular and adding the public keyword in so many places would decrease Kotlin’s brevity. It made both DSLs and constructors cleaner and shorter.
Some of the suggested solutions:
- creating multiple private classes per file: compared to Java, in Kotlin you can create multiple classes in one source file. So the solution to the lack of package private would be to accumulate more private classes in one source file. However this leads to unreasonable growth of the size of individual source files. Also, what if you want to have some common logic between two classes? You need to either put them in the same file (may violate SRP) or make them public.
Another issue with this solution is testing, as you cannot test private constructs. One might say that you should not use internal interfaces for testing anyway, but I believe it is better to have control on the level at which you want to test, especially when testing complex logic with a large input and output space.
- creating separate modules: if you want something to be a private implementation, you can create a module and mark the class as internal. Creating submodules is a good idea and can help at some level, but it may not be very scalable. If you have a very big codebase, relying only on modularity to organize and encapsulate your code may not be enough. Creating a submodule every time you wanted to use package private would lead to a lots of small modules, and then even more modules when you start resolving circular dependencies. This does not seem very scalable.
Manifest vs Modifier
One interesting difference when it comes to comparing visibility in Kotlin and Java is how you expose a module’s external interface. As mentioned before, in Kotlin every class is exposed outside of the module by default. To change the default visibility you need to add an access modifier.
In Java, since version 9, you expose a module’s external interface by specifying exported packages in the module’s manifest file.
Which one is better? Although Kotlin gives more granular control over which classes we want to expose, I like the Java solution better. Having an external interface defined in a manifest file makes the decision more conscious. You need to manually specify your interface (although you might accidentally create a public class in an exported package). Also there is only one place that you need to check in order to know what you are exposing.
What does it all mean?
As you can see, the lack of package private is a drawback that can be easily fixed by the creators of Kotlin. There is an ongoing discussion about this in the Kotlin community , and hopefully in time we will see it implemented.
A more important issue is the issue of the default public visibility. Kotlin has some valid reasons for making this decision. The pros and cons of this approach are evident, but it’s hard to say definitively which approach is better. Deciding to make everything public by default makes Kotlin better for DSLs and (I believe) relatively small codebases. This was a conscious decision made with a certain audience and use cases in mind.
Regardless of this, Kotlin is a great alternative to Java, making the development of both large and small applications quicker and more pleasant. It is well thought out and fun to work with, so if you are a Java developer I encourage you to try it.
Edited: fixed information about inter-package access.