2

To give some context to the problem I'm trying to solve, I have two separate projects...

The first contains a set of APIs which are mostly defined via interfaces and contains very little implementation. This should be considered something that is consumable as a dependency; for example:

project-api

package com.newco.api.users;

public interface UserService {
    void createUser(User user);
    User getUserById(UUID id);
}
package com.newco.api.accounts;

public interface AccountService {
    void createUserAccount(User user, Account account);
    Account getAccountByUser(User user);
}

Users may consume these interfaces as part of the API. Note the relationship between users and accounts.

The second project contains the implementation of the APIs. These are not consumable and should never be seen by a developer; for example:

project-implementation

package com.newco.impl.users;

import com.newco.api.users.UserService;

final class UserServiceImpl implements UserService {

    @Override
    public void createUser(User user) { ... }

    @Override
    public User getUserById(UUID id) { ... }
}
package com.newco.impl.accounts;

import com.newco.api.users.UserService;
import com.newco.api.accounts.AccountService;
// This is problematic (see below)...
import com.newco.impl.users.UserSeviceImpl;

final class AccountServiceImpl implements AccountService {

    // This is problematic (see below)...
    private final UserService userService = new UserServiceImpl();

    @Override
    public void createUserAccount(User user, Account account) { ... }

    @Override
    public Account getAccountByUser(User user) { ... }
}

Putting aside the obvious violation of the Dependency Inversion Principle, as it really isn't important for the sake of this question, the problem is that I can't access UserServiceImpl from AccountServiceImpl because it's package-private - I can't even import it.

In contrast, if I were to implement this with Kotlin, the problem goes away entirely; for example:

project-implementation-kotlin

package com.newco.impl.users

import com.newco.api.users.UserService

internal class UserServiceImpl : UserService {

    fun createUser(user: User) { ... }

    fun getUserById(id: UUID): User { ... }
}
package com.newco.impl.accounts

import com.newco.api.users.UserService
import com.newco.api.accounts.AccountService
import com.newco.impl.users.UserSeviceImpl

internal class AccountServiceImpl implements AccountService {

    private val userService = UserServiceImpl()

    fun createUserAccount(user: User, account: Account) { ... }

    fun getAccountByUser(user: User): Account { ... }
}

My expectation was that Java's package-private accessibility works the same way as Kotlin's internal modifier, but it seems they are subtly different.

Currently, it seems that if I want to implement these interfaces with Java, I have two choices:

  1. I make the classes public, and therefore accessible, but am able to organise the classes into separate packages.

  2. I can make the classes package-private, and therefore inaccessible, but am not able to organise them into separate packages.

Neither of these choices are ideal. I would rather keep the implementations inaccessible, and organised.

How does internal differ from package-private, and is there a way to achieve the equivalent of internal in Java? Do Java 9 modules solve this problem in any way?

Note: I'm targeting Java 11

Matthew Layton
  • 39,871
  • 52
  • 185
  • 313
  • 2
    Ah I think I misunderstood your question entirely. I thought you meant how to do package-private in Kotlin. Doing `internal` in Java is of course possible. Just don't `exports` the packages you want - `internal` is the default behaviour if you have a `module-info.java`. Just `exports` the packages you want to be public. – Sweeper May 26 '23 at 11:32

1 Answers1

2

internal Kotlin class members are visible in the same module.

See Visibility Modifiers

  • internal means that any client inside this module who sees the declaring class sees its internal members.

A module is "a set of Kotlin files compiled together". The documentation gives some examples at the very end:

  • An IntelliJ IDEA module.
  • A Maven project.
  • A Gradle source set (with the exception that the test source set can access the internal declarations of main).
  • A set of files compiled with one invocation of the <kotlinc> Ant task.

This is a bit similar to how internal is in C#.

There is no internal access modifier in Java, but everything public in a Java module are internal, in the sense that other modules cannot access it. Other modules can only see what a module exports. This is something you can specify in a module-info.java file, located in your source root.

So in your situation you can have the module that has the interfaces export the interfaces, and have the implementations only export the parts that you don't want to be internal.

Example module-info.java files:

For project.api:

module project.api {
    exports com.newco.api.users;
    exports com.newco.api.accounts;
}

For project.impl;

module project.impl {
    requires project.api;
    exports the.parts.you.dont.want.to.be.internal;
    uses AccountService;
    uses UserService;
}

And consumers can say requires project.impl, and still can only access the public declarations in the the.parts.you.dont.want.to.be.internal package.

Sweeper
  • 213,210
  • 22
  • 193
  • 313
  • "_If you override a package-private Java method, it becomes protected, **in both Java** and Kotlin_" -- Maybe I'm not understanding properly, but in Java, overriding a package-private method does not automatically make it protected. You can manually make the override protected, or even public, but that doesn't happen automatically. – Slaw May 26 '23 at 10:09
  • In my case, I have some Java classes that implement interfaces, but I never want those implementations to ever be visible; they will only ever be accessible via the interfaces. Some of those implementations depend on one another. So I have to decide between either good package organisation and public classes (which makes them visible), or poor package organisation but no visibility. There seems to be no in-between, unless I'm missing something? – Matthew Layton May 26 '23 at 10:16
  • 1
    @Slaw sorry I meant the Kotlin declaration will be `protected`, in the Java sense, from Java’s POV, and `protected`, in the Kotlin sense, from Kotlin’s POV. – Sweeper May 26 '23 at 10:24
  • @MatthewLayton so this turned out to be an XY problem? Anyway, it doesn’t sound like that is possible, but without seeing any code, I can’t really be sure. – Sweeper May 26 '23 at 10:27
  • @Sweeper with this potentially being an XY problem, I've updated the question with a lot more detail (which I probably should have done in the first place). – Matthew Layton May 26 '23 at 11:12