If ServiceLoader
mostly fits your needs, that says that you're looking for service discovery via the presence of files on the class path. That's only a small part of what OSGi provides.
OSGi will let you dynamically install bundles, advertise services, revoke advertisements, and uninstall bundles all while the application is running. Furthermore, as a consumer of services, you can look them up eagerly -- with filtering predicate queries -- and detect when offered service providers come and go. These bundles need not lie on the class path, and they can be provided in various forms; Jar files and "exploded directories" are the two I recall.
By contrast, ServiceLoader
does just one thing: it exposes discoverable factories. Usually you'll create a factory-style interface that takes some argument to decide whether that provider can offer the appropriate service, such as mapping a given character set name to a CharsetDecoder
. There's no formalized protocol for acquiring and releasing a service from such a provider. OSGi does formalize the binding and unbinding of consumers to services. The consumer can receive notification when new providers come online, and the provider can receive notification when a consumer acquires and releases a service instance. If this life-cycle control is important to your service and you forgo OSGi, you'll have to build this yourself; ServiceLoader
doesn't go that far.
Alternately, rather than eager service lookup and use, you can take a more passive, declarative approach and let one of the OSGi dependency managers match your stated needs to the available service providers. There are many dependency managers to choose from. Spring Dynamic Modules is one of the most capable.
OSGi provides many other "middleware" facilities. I won't try to sell you on them here, as your question focuses mostly on what you'd be missing out on by choosing ServiceLoader
.