Java
Completed
-
Section 1: Getting Started
-
Section 2: Core Syntax
-
Section 3: Object-Oriented Java
-
Section 4: Collections Framework
-
Section 5: Exception Handling
-
Section 6: Generics
-
Section 7: Functional Java
-
Section 8: Concurrency
-
Section 9: I/O and NIO
-
Section 10: JVM Internals
-
Section 11: Modern Java Features
-
Section 12: Build Tools and Project Structure
-
Section 13: Testing
-
Section 14: Databases and Persistence
-
Section 15: Networking
-
Section 16: Design and Best Practices
-
Section 17: Reflection and Annotations
-
Section 18: Logging and Diagnostics
-
Section 19: Date, Time, and Internationalization
-
Section 20: Java Platform Module System
-
Section 21: Security in Java
-
Section 22: Advanced Collections and Data Structures
-
Section 23: More Concurrency Patterns
-
Section 24: Compression, Files, and System Integration
-
Section 25: GUI Programming
-
Section 26: Practical Projects
-
Section 27: Data Structures and Algorithms
-
Section 28: Interview and Algorithm Practice
-
Section 29: JSON and Data Interchange
-
Section 30: More Concurrency Utilities
-
Section 31: More Collections and Streams Practice
-
Section 32: More File and System Programming
-
Section 33: Standard Library Deep Dive
-
Section 34: More Practice and Drills
-
Section 35: More Testing and Quality
-
Section 36: More Design Patterns and Architecture
-
Section 37: Career and Ecosystem
-
Section 38: More OOP and Architecture Practice
-
Section 39: More Enterprise Concepts
-
Section 40: Advanced JavaFX
-
Section 41: More Interview Practice
184: Services and Service Loaders in JPMS
I've been working on a payment gateway for a client, and I hit a wall today. I have a core module that handles checkouts, but I need to support multiple payment providers—Stripe, PayPal, and maybe some local bank APIs later. My first instinct was to just create a list of implementations and instantiate them manually, but that creates a nightmare of dependencies. I don't want my checkout-core module to have to know every single single provider class that exists in the ecosystem.
The Hard-Coded Wall
I started with a simple interface in my com.payment.api module:
public interface PaymentProcessor {
void process(double amount);
}
Then, in a separate module com.payment.stripe, I implemented it. Naturally, I tried to call it from my main app like this:
PaymentProcessor processor = new StripeProcessor();
processor.process(100.0);
This works, but it's brittle. If I want to add PayPal, I have to modify the main app's code, recompile, and add another dependency. That's not a plugin system; that's just a hard-coded list. I want the app to say, "Give me whatever payment processors are available on the classpath/module-path," and have Java just figure it out.
The Silent Failure of ServiceLoader
I remember reading about ServiceLoader, so I decided to give it a shot. I deleted the new StripeProcessor() line and replaced it with this:
ServiceLoader<PaymentProcessor> loader = ServiceLoader.load(PaymentProcessor.class);
for (PaymentProcessor p : loader) {
p.process(100.0);
}
I ran it. Nothing happened. No crash, no exception—just total silence. The loop didn't execute a single time. I checked my imports, I checked the JARs, and everything looked correct. In the pre-module days, I would have just dropped a file into META-INF/services and called it a day, but we're in the JPMS world now. The module system is much stricter about who can see what.
Closing the Gap in module-info
I realized the checkout-core module doesn't actually have permission to "use" the service, and the stripe module hasn't officially "provided" it. The code is there, but the module metadata is missing.
First, I went into the checkout-core module's module-info.java. I need to tell Java that this module intends to consume a service. I added the uses keyword:
module com.payment.core {
requires com.payment.api;
uses com.payment.api.PaymentProcessor;
}
Still nothing. I ran it again, and the list was still empty. Right, because the implementation module—com.payment.stripe—is still keeping its implementation class private (as it should be). I need to explicitly export that implementation as a service provider.
I opened the module-info.java for the Stripe module and added the provides...with clause:
module com.payment.stripe {
requires com.payment.api;
provides com.payment.api.PaymentProcessor
with com.payment.stripe.StripeProcessor;
}
The Payoff
I fired it up one more time. This time, the console lit up: "Processing $100.0 via Stripe". The magic here is that checkout-core still has absolutely no dependency on com.payment.stripe. If I create a new com.payment.paypal module and add it to the module path, the ServiceLoader will pick it up automatically without me changing a single line of code in the core logic.
It's a decoupled architecture. The uses statement in the consumer tells the JVM to look for providers, and the provides...with statement in the provider registers the implementation. It's a clean handshake that keeps my core logic lean and my plugins truly pluggable.
📋 Practical Task
Build a Pluggable Logging Framework
You need to create a mini-plugin system for a logging framework using JPMS. Your goal is to allow the main application to log messages without knowing which logging implementation is being used (Console, File, or Database).
- Module 1 (
com.logger.api): Define an interfaceLoggerServicewith a methodvoid log(String message). - Module 2 (
com.logger.impl.console): Create a classConsoleLoggerthat implementsLoggerServiceand prints toSystem.out. Configure themodule-info.javato provide this service. - Module 3 (
com.logger.app): Create a main class that usesServiceLoaderto find all availableLoggerServiceimplementations and calls thelogmethod on each. Configure themodule-info.javato use theLoggerService.
Verification: Run the app and verify that "Hello from ConsoleLogger" is printed. Then, create a second implementation module (e.g., com.logger.impl.file) and add it to the module path to see if the app picks up both loggers without needing a recompile of the com.logger.app module.
There are no comments for now.