Listing Interface implementations is overcomplicated

Reflection in JVM languages seems to be really bad.
A number of techniques relies on essentially being able to retrieve the list of Interface or abstract class implementations.
Sure, this can be done with a sealed class, but they need to be a part of the same package.

When you need more than this, Java and others call them Services or plugins. The ServiceLoader written to support this requires you to bake a lookup table into the JAR, e.g. META-INF/services/my.com.package.MyInterface

ServiceLoader was unfortunately written in a time before lambda expressions and other nice features, so when your service has no default constructor, you end up getting an error like Unable to get public no-arg constructor and you can’t do anything about it.

But all this is besides the point–all of this is far too complicated for something that should be “easy” in a statically typed language with modules.
What is missing from the language to simply be able to use reflection to get the list of Interfaces, i.e. MyInterface::subclasses?
Supposing it were impossible, what would an idiomatic Kotlin ServiceLoader look like?

Java authors made such operations tricky by designing the class loading as it is. Class loaders in JVM generally don’t know the list of classes, list of packages, etc., they work on on-demand basis:

  • Give me com.example.Foo class!
  • Looking for it… Ok, here it is!

So we need to provide them a name and they can look for it, but otherwise they can’t find the class by themselves.

ServiceLoader is a good example on how to provide a plugin functionality in such a on-demand environment. You ask for MyInterface implementations, so ServiceLoader looks for META-INF/services/MyInterface (it creates the name of the file from the name of the interface), it reads the file, finds names of implementations, then look for them. In the whole process we always have a name for the next item to work with. We don’t have to list files or guess their names.

Such design makes things really tricky for dependency injection frameworks, web frameworks and other kinds of frameworks that use annotations for configuration. I believe they use some workarounds to be able to list all classes, but this is not 100% reliable.

Also, I don’t understand how no-args constructor is incompatible with lambdas and other modern features. If there is some kind of utility or a framework which instantiates your classes for you, then it is pretty normal thing that it requires a no-arg constructor. This is common in deserialization libraries, in dependency injection, in Android component classes, in managed beans and in other types of managed objects, etc.