DEV Community

Cover image for Java SPI: Build Flexible Enterprise Applications with Service Provider Interface Design Patterns
Aarav Joshi
Aarav Joshi

Posted on

Java SPI: Build Flexible Enterprise Applications with Service Provider Interface Design Patterns

As a best-selling author, I invite you to explore my books on Amazon. Don't forget to follow me on Medium and show your support. Thank you! Your support means the world!

Java's Service Provider Interface represents one of the most powerful yet underutilized patterns for creating flexible software architectures. I've spent years working with various extensibility patterns, and SPI consistently proves its worth in enterprise applications where modularity and adaptability matter most.

The fundamental concept behind SPI revolves around separating service definitions from their implementations. This separation creates opportunities for dynamic behavior modification, plugin architectures, and seamless integration of third-party components. Modern applications demand this flexibility to remain competitive and maintainable.

Understanding the Foundation

SPI operates through a simple yet elegant mechanism. You define service contracts using interfaces, implement these contracts in separate classes, and register implementations through META-INF configuration files. The Java runtime handles discovery and instantiation automatically.

public interface PaymentProcessor {
    boolean processPayment(PaymentRequest request);
    String getProviderName();
    boolean supports(PaymentMethod method);
}

public class CreditCardProcessor implements PaymentProcessor {
    @Override
    public boolean processPayment(PaymentRequest request) {
        // Credit card processing logic
        System.out.println("Processing credit card payment: " + request.getAmount());
        return true;
    }

    @Override
    public String getProviderName() {
        return "CreditCard";
    }

    @Override
    public boolean supports(PaymentMethod method) {
        return method == PaymentMethod.CREDIT_CARD;
    }
}

public class PayPalProcessor implements PaymentProcessor {
    @Override
    public boolean processPayment(PaymentRequest request) {
        // PayPal processing logic
        System.out.println("Processing PayPal payment: " + request.getAmount());
        return true;
    }

    @Override
    public String getProviderName() {
        return "PayPal";
    }

    @Override
    public boolean supports(PaymentMethod method) {
        return method == PaymentMethod.PAYPAL;
    }
}
Enter fullscreen mode Exit fullscreen mode

The configuration file /META-INF/services/com.example.PaymentProcessor lists available implementations:

com.example.CreditCardProcessor
com.example.PayPalProcessor
Enter fullscreen mode Exit fullscreen mode

Loading and using these services becomes straightforward:

public class PaymentService {
    private final Map<PaymentMethod, PaymentProcessor> processors;

    public PaymentService() {
        processors = new HashMap<>();
        loadProcessors();
    }

    private void loadProcessors() {
        ServiceLoader<PaymentProcessor> loader = ServiceLoader.load(PaymentProcessor.class);
        for (PaymentProcessor processor : loader) {
            System.out.println("Loaded processor: " + processor.getProviderName());
            // Register processor for supported methods
            for (PaymentMethod method : PaymentMethod.values()) {
                if (processor.supports(method)) {
                    processors.put(method, processor);
                }
            }
        }
    }

    public boolean processPayment(PaymentRequest request) {
        PaymentProcessor processor = processors.get(request.getMethod());
        if (processor != null) {
            return processor.processPayment(request);
        }
        throw new UnsupportedOperationException("No processor for method: " + request.getMethod());
    }
}
Enter fullscreen mode Exit fullscreen mode

Pattern One: Configuration-Driven Service Selection

Configuration-driven selection allows applications to choose implementations based on external settings. This pattern proves invaluable when different environments require different service behaviors.

public interface DatabaseConnector {
    Connection getConnection();
    String getConnectionType();
    boolean isConfigured(Properties config);
}

public class MySQLConnector implements DatabaseConnector {
    private Properties config;

    public MySQLConnector() {
        this.config = loadConfiguration();
    }

    @Override
    public Connection getConnection() {
        try {
            return DriverManager.getConnection(
                config.getProperty("mysql.url"),
                config.getProperty("mysql.username"),
                config.getProperty("mysql.password")
            );
        } catch (SQLException e) {
            throw new RuntimeException("Failed to connect to MySQL", e);
        }
    }

    @Override
    public String getConnectionType() {
        return "mysql";
    }

    @Override
    public boolean isConfigured(Properties config) {
        return config.containsKey("mysql.url") && 
               config.containsKey("mysql.username");
    }

    private Properties loadConfiguration() {
        Properties props = new Properties();
        try (InputStream input = getClass().getResourceAsStream("/database.properties")) {
            if (input != null) {
                props.load(input);
            }
        } catch (IOException e) {
            System.err.println("Failed to load database configuration");
        }
        return props;
    }
}

public class PostgreSQLConnector implements DatabaseConnector {
    private Properties config;

    public PostgreSQLConnector() {
        this.config = loadConfiguration();
    }

    @Override
    public Connection getConnection() {
        try {
            return DriverManager.getConnection(
                config.getProperty("postgresql.url"),
                config.getProperty("postgresql.username"),
                config.getProperty("postgresql.password")
            );
        } catch (SQLException e) {
            throw new RuntimeException("Failed to connect to PostgreSQL", e);
        }
    }

    @Override
    public String getConnectionType() {
        return "postgresql";
    }

    @Override
    public boolean isConfigured(Properties config) {
        return config.containsKey("postgresql.url") && 
               config.containsKey("postgresql.username");
    }

    private Properties loadConfiguration() {
        Properties props = new Properties();
        try (InputStream input = getClass().getResourceAsStream("/database.properties")) {
            if (input != null) {
                props.load(input);
            }
        } catch (IOException e) {
            System.err.println("Failed to load database configuration");
        }
        return props;
    }
}
Enter fullscreen mode Exit fullscreen mode

The service selector chooses implementations based on configuration availability:

public class DatabaseService {
    private DatabaseConnector connector;

    public DatabaseService() {
        selectConnector();
    }

    private void selectConnector() {
        Properties config = loadApplicationConfig();
        String preferredType = config.getProperty("database.type", "auto");

        ServiceLoader<DatabaseConnector> loader = ServiceLoader.load(DatabaseConnector.class);

        if (!"auto".equals(preferredType)) {
            // Use explicitly configured connector
            for (DatabaseConnector candidate : loader) {
                if (preferredType.equals(candidate.getConnectionType())) {
                    connector = candidate;
                    System.out.println("Selected configured connector: " + preferredType);
                    return;
                }
            }
        }

        // Auto-select based on available configuration
        for (DatabaseConnector candidate : loader) {
            if (candidate.isConfigured(config)) {
                connector = candidate;
                System.out.println("Auto-selected connector: " + candidate.getConnectionType());
                return;
            }
        }

        throw new RuntimeException("No suitable database connector found");
    }

    public Connection getConnection() {
        return connector.getConnection();
    }

    private Properties loadApplicationConfig() {
        Properties props = new Properties();
        try (InputStream input = getClass().getResourceAsStream("/application.properties")) {
            if (input != null) {
                props.load(input);
            }
        } catch (IOException e) {
            System.err.println("Failed to load application configuration");
        }
        return props;
    }
}
Enter fullscreen mode Exit fullscreen mode

Pattern Two: Multiple Provider Coexistence

Multiple providers can coexist within the same application, enabling feature flags, A/B testing, and gradual rollouts. This pattern supports experimentation without disrupting existing functionality.

public interface LoggingProvider {
    void log(LogLevel level, String message, Object... args);
    String getProviderName();
    int getPriority();
    boolean isEnabled();
}

public class ConsoleLoggingProvider implements LoggingProvider {
    @Override
    public void log(LogLevel level, String message, Object... args) {
        String formatted = String.format("[%s] %s: %s", 
            System.currentTimeMillis(), level, String.format(message, args));
        System.out.println(formatted);
    }

    @Override
    public String getProviderName() {
        return "console";
    }

    @Override
    public int getPriority() {
        return 100;
    }

    @Override
    public boolean isEnabled() {
        return true;
    }
}

public class FileLoggingProvider implements LoggingProvider {
    private final String logFile;

    public FileLoggingProvider() {
        this.logFile = System.getProperty("log.file", "application.log");
    }

    @Override
    public void log(LogLevel level, String message, Object... args) {
        String formatted = String.format("[%s] %s: %s%n", 
            System.currentTimeMillis(), level, String.format(message, args));

        try (FileWriter writer = new FileWriter(logFile, true)) {
            writer.write(formatted);
        } catch (IOException e) {
            System.err.println("Failed to write to log file: " + e.getMessage());
        }
    }

    @Override
    public String getProviderName() {
        return "file";
    }

    @Override
    public int getPriority() {
        return 200;
    }

    @Override
    public boolean isEnabled() {
        return Boolean.parseBoolean(System.getProperty("file.logging.enabled", "true"));
    }
}

public class ExperimentalCloudLoggingProvider implements LoggingProvider {
    @Override
    public void log(LogLevel level, String message, Object... args) {
        // Simulate cloud logging
        System.out.println("CLOUD: " + String.format(message, args));
    }

    @Override
    public String getProviderName() {
        return "experimental-cloud";
    }

    @Override
    public int getPriority() {
        return 300;
    }

    @Override
    public boolean isEnabled() {
        return Boolean.parseBoolean(System.getProperty("experimental.logging.enabled", "false"));
    }
}
Enter fullscreen mode Exit fullscreen mode

The logging service uses all enabled providers simultaneously:

public class LoggingService {
    private final List<LoggingProvider> providers;

    public LoggingService() {
        providers = new ArrayList<>();
        loadProviders();
    }

    private void loadProviders() {
        ServiceLoader<LoggingProvider> loader = ServiceLoader.load(LoggingProvider.class);

        for (LoggingProvider provider : loader) {
            if (provider.isEnabled()) {
                providers.add(provider);
                System.out.println("Enabled logging provider: " + provider.getProviderName());
            } else {
                System.out.println("Disabled logging provider: " + provider.getProviderName());
            }
        }

        // Sort by priority (higher priority first)
        providers.sort((a, b) -> Integer.compare(b.getPriority(), a.getPriority()));
    }

    public void log(LogLevel level, String message, Object... args) {
        for (LoggingProvider provider : providers) {
            try {
                provider.log(level, message, args);
            } catch (Exception e) {
                System.err.println("Logging provider failed: " + provider.getProviderName() + 
                                 " - " + e.getMessage());
            }
        }
    }

    public void info(String message, Object... args) {
        log(LogLevel.INFO, message, args);
    }

    public void error(String message, Object... args) {
        log(LogLevel.ERROR, message, args);
    }
}
Enter fullscreen mode Exit fullscreen mode

Pattern Three: Dynamic Service Reloading

Dynamic reloading enables applications to discover new implementations without restarts. This pattern supports hot-swapping of functionality in long-running applications.

public interface PluginService {
    void execute(Map<String, Object> context);
    String getPluginName();
    String getVersion();
    boolean isCompatible(String apiVersion);
}

public class EmailNotificationPlugin implements PluginService {
    @Override
    public void execute(Map<String, Object> context) {
        String recipient = (String) context.get("email");
        String message = (String) context.get("message");

        System.out.println("Sending email to: " + recipient);
        System.out.println("Message: " + message);
        // Email sending logic here
    }

    @Override
    public String getPluginName() {
        return "email-notification";
    }

    @Override
    public String getVersion() {
        return "1.0.0";
    }

    @Override
    public boolean isCompatible(String apiVersion) {
        return "1.0".equals(apiVersion);
    }
}

public class SlackNotificationPlugin implements PluginService {
    @Override
    public void execute(Map<String, Object> context) {
        String channel = (String) context.get("channel");
        String message = (String) context.get("message");

        System.out.println("Posting to Slack channel: " + channel);
        System.out.println("Message: " + message);
        // Slack API integration here
    }

    @Override
    public String getPluginName() {
        return "slack-notification";
    }

    @Override
    public String getVersion() {
        return "2.1.0";
    }

    @Override
    public boolean isCompatible(String apiVersion) {
        return "1.0".equals(apiVersion) || "2.0".equals(apiVersion);
    }
}
Enter fullscreen mode Exit fullscreen mode

The plugin manager supports dynamic reloading:

public class PluginManager {
    private final String apiVersion = "1.0";
    private final Map<String, PluginService> plugins;
    private final ScheduledExecutorService scheduler;
    private volatile ServiceLoader<PluginService> serviceLoader;

    public PluginManager() {
        this.plugins = new ConcurrentHashMap<>();
        this.scheduler = Executors.newScheduledThreadPool(1);
        this.serviceLoader = ServiceLoader.load(PluginService.class);

        loadPlugins();
        startPeriodicReload();
    }

    private void loadPlugins() {
        // Reload the service loader to discover new plugins
        serviceLoader.reload();

        Map<String, PluginService> newPlugins = new HashMap<>();

        for (PluginService plugin : serviceLoader) {
            if (plugin.isCompatible(apiVersion)) {
                newPlugins.put(plugin.getPluginName(), plugin);
                System.out.println("Loaded plugin: " + plugin.getPluginName() + 
                                 " v" + plugin.getVersion());
            } else {
                System.out.println("Incompatible plugin: " + plugin.getPluginName() + 
                                 " v" + plugin.getVersion());
            }
        }

        // Update the plugin registry
        plugins.clear();
        plugins.putAll(newPlugins);

        System.out.println("Total plugins loaded: " + plugins.size());
    }

    private void startPeriodicReload() {
        scheduler.scheduleAtFixedRate(() -> {
            try {
                loadPlugins();
            } catch (Exception e) {
                System.err.println("Failed to reload plugins: " + e.getMessage());
            }
        }, 30, 30, TimeUnit.SECONDS);
    }

    public void executePlugin(String pluginName, Map<String, Object> context) {
        PluginService plugin = plugins.get(pluginName);
        if (plugin != null) {
            try {
                plugin.execute(context);
            } catch (Exception e) {
                System.err.println("Plugin execution failed: " + pluginName + 
                                 " - " + e.getMessage());
            }
        } else {
            System.err.println("Plugin not found: " + pluginName);
        }
    }

    public Set<String> getAvailablePlugins() {
        return new HashSet<>(plugins.keySet());
    }

    public void shutdown() {
        scheduler.shutdown();
        try {
            if (!scheduler.awaitTermination(5, TimeUnit.SECONDS)) {
                scheduler.shutdownNow();
            }
        } catch (InterruptedException e) {
            scheduler.shutdownNow();
            Thread.currentThread().interrupt();
        }
    }
}
Enter fullscreen mode Exit fullscreen mode

Pattern Four: Extension Point Standardization

Extension points provide standardized interfaces for third-party developers. This pattern enables ecosystem development around your application core.

public interface DataTransformer {
    TransformationResult transform(DataInput input);
    String getTransformerType();
    Set<String> getSupportedInputFormats();
    Set<String> getSupportedOutputFormats();
    TransformerMetadata getMetadata();
}

public class TransformerMetadata {
    private final String name;
    private final String description;
    private final String version;
    private final String author;

    public TransformerMetadata(String name, String description, String version, String author) {
        this.name = name;
        this.description = description;
        this.version = version;
        this.author = author;
    }

    // Getters
    public String getName() { return name; }
    public String getDescription() { return description; }
    public String getVersion() { return version; }
    public String getAuthor() { return author; }
}

public class TransformationResult {
    private final boolean success;
    private final Object data;
    private final String outputFormat;
    private final String errorMessage;

    private TransformationResult(boolean success, Object data, String outputFormat, String errorMessage) {
        this.success = success;
        this.data = data;
        this.outputFormat = outputFormat;
        this.errorMessage = errorMessage;
    }

    public static TransformationResult success(Object data, String outputFormat) {
        return new TransformationResult(true, data, outputFormat, null);
    }

    public static TransformationResult failure(String errorMessage) {
        return new TransformationResult(false, null, null, errorMessage);
    }

    // Getters
    public boolean isSuccess() { return success; }
    public Object getData() { return data; }
    public String getOutputFormat() { return outputFormat; }
    public String getErrorMessage() { return errorMessage; }
}

public class DataInput {
    private final Object data;
    private final String format;
    private final Map<String, Object> parameters;

    public DataInput(Object data, String format, Map<String, Object> parameters) {
        this.data = data;
        this.format = format;
        this.parameters = parameters != null ? parameters : new HashMap<>();
    }

    // Getters
    public Object getData() { return data; }
    public String getFormat() { return format; }
    public Map<String, Object> getParameters() { return parameters; }
}
Enter fullscreen mode Exit fullscreen mode

Sample transformer implementations:

public class JsonToXmlTransformer implements DataTransformer {
    @Override
    public TransformationResult transform(DataInput input) {
        try {
            String jsonData = (String) input.getData();
            // Simulate JSON to XML conversion
            String xmlData = "<root>" + jsonData.replace("{", "<item>").replace("}", "</item>") + "</root>";
            return TransformationResult.success(xmlData, "xml");
        } catch (Exception e) {
            return TransformationResult.failure("JSON to XML transformation failed: " + e.getMessage());
        }
    }

    @Override
    public String getTransformerType() {
        return "json-to-xml";
    }

    @Override
    public Set<String> getSupportedInputFormats() {
        return Set.of("json");
    }

    @Override
    public Set<String> getSupportedOutputFormats() {
        return Set.of("xml");
    }

    @Override
    public TransformerMetadata getMetadata() {
        return new TransformerMetadata(
            "JSON to XML Transformer",
            "Converts JSON data structures to XML format",
            "1.0.0",
            "Internal Team"
        );
    }
}

public class CsvToJsonTransformer implements DataTransformer {
    @Override
    public TransformationResult transform(DataInput input) {
        try {
            String csvData = (String) input.getData();
            // Simulate CSV to JSON conversion
            String[] lines = csvData.split("\n");
            StringBuilder json = new StringBuilder("[");

            for (int i = 1; i < lines.length; i++) {
                if (i > 1) json.append(",");
                json.append("{\"data\":\"").append(lines[i]).append("\"}");
            }
            json.append("]");

            return TransformationResult.success(json.toString(), "json");
        } catch (Exception e) {
            return TransformationResult.failure("CSV to JSON transformation failed: " + e.getMessage());
        }
    }

    @Override
    public String getTransformerType() {
        return "csv-to-json";
    }

    @Override
    public Set<String> getSupportedInputFormats() {
        return Set.of("csv");
    }

    @Override
    public Set<String> getSupportedOutputFormats() {
        return Set.of("json");
    }

    @Override
    public TransformerMetadata getMetadata() {
        return new TransformerMetadata(
            "CSV to JSON Transformer",
            "Converts CSV files to JSON array format",
            "1.2.1",
            "Data Processing Team"
        );
    }
}
Enter fullscreen mode Exit fullscreen mode

The transformation service manages registered transformers:

public class TransformationService {
    private final Map<String, DataTransformer> transformers;
    private final Map<String, Set<DataTransformer>> inputFormatIndex;
    private final Map<String, Set<DataTransformer>> outputFormatIndex;

    public TransformationService() {
        this.transformers = new HashMap<>();
        this.inputFormatIndex = new HashMap<>();
        this.outputFormatIndex = new HashMap<>();

        loadTransformers();
    }

    private void loadTransformers() {
        ServiceLoader<DataTransformer> loader = ServiceLoader.load(DataTransformer.class);

        for (DataTransformer transformer : loader) {
            registerTransformer(transformer);
        }

        System.out.println("Loaded " + transformers.size() + " transformers");
    }

    private void registerTransformer(DataTransformer transformer) {
        transformers.put(transformer.getTransformerType(), transformer);

        // Index by input formats
        for (String inputFormat : transformer.getSupportedInputFormats()) {
            inputFormatIndex.computeIfAbsent(inputFormat, k -> new HashSet<>()).add(transformer);
        }

        // Index by output formats
        for (String outputFormat : transformer.getSupportedOutputFormats()) {
            outputFormatIndex.computeIfAbsent(outputFormat, k -> new HashSet<>()).add(transformer);
        }

        TransformerMetadata metadata = transformer.getMetadata();
        System.out.println("Registered transformer: " + metadata.getName() + 
                         " v" + metadata.getVersion() + " by " + metadata.getAuthor());
    }

    public TransformationResult transform(DataInput input, String targetFormat) {
        Set<DataTransformer> candidates = inputFormatIndex.get(input.getFormat());

        if (candidates == null || candidates.isEmpty()) {
            return TransformationResult.failure("No transformer found for input format: " + input.getFormat());
        }

        for (DataTransformer transformer : candidates) {
            if (transformer.getSupportedOutputFormats().contains(targetFormat)) {
                return transformer.transform(input);
            }
        }

        return TransformationResult.failure("No transformer found for conversion from " + 
                                          input.getFormat() + " to " + targetFormat);
    }

    public List<TransformerMetadata> getAvailableTransformers() {
        return transformers.values().stream()
                          .map(DataTransformer::getMetadata)
                          .collect(Collectors.toList());
    }

    public Set<String> getSupportedInputFormats() {
        return new HashSet<>(inputFormatIndex.keySet());
    }

    public Set<String> getSupportedOutputFormats() {
        return new HashSet<>(outputFormatIndex.keySet());
    }
}
Enter fullscreen mode Exit fullscreen mode

Pattern Five: Contextual Service Resolution

Contextual resolution selects services based on runtime conditions and request characteristics. This pattern enables intelligent service routing and adaptive behavior.

public interface AuthenticationProvider {
    AuthenticationResult authenticate(AuthenticationContext context);
    boolean supports(AuthenticationContext context);
    String getProviderName();
    int getConfidenceLevel();
}

public class AuthenticationContext {
    private final String username;
    private final String password;
    private final String clientIp;
    private final String userAgent;
    private final Map<String, Object> attributes;

    public AuthenticationContext(String username, String password, String clientIp, 
                               String userAgent, Map<String, Object> attributes) {
        this.username = username;
        this.password = password;
        this.clientIp = clientIp;
        this.userAgent = userAgent;
        this.attributes = attributes != null ? attributes : new HashMap<>();
    }

    // Getters
    public String getUsername() { return username; }
    public String getPassword() { return password; }
    public String getClientIp() { return clientIp; }
    public String getUserAgent() { return userAgent; }
    public Map<String, Object> getAttributes() { return attributes; }
}

public class AuthenticationResult {
    private final boolean success;
    private final String userId;
    private final String message;
    private final Map<String, Object> metadata;

    private AuthenticationResult(boolean success, String userId, String message, 
                                Map<String, Object> metadata) {
        this.success = success;
        this.userId = userId;
        this.message = message;
        this.metadata = metadata != null ? metadata : new HashMap<>();
    }

    public static AuthenticationResult success(String userId, Map<String, Object> metadata) {
        return new AuthenticationResult(true, userId, "Authentication successful", metadata);
    }

    public static AuthenticationResult failure(String message) {
        return new AuthenticationResult(false, null, message, null);
    }

    // Getters
    public boolean isSuccess() { return success; }
    public String getUserId() { return userId; }
    public String getMessage() { return message; }
    public Map<String, Object> getMetadata() { return metadata; }
}
Enter fullscreen mode Exit fullscreen mode

Different authentication providers for various contexts:

public class DatabaseAuthenticationProvider implements AuthenticationProvider {
    @Override
    public AuthenticationResult authenticate(AuthenticationContext context) {
        // Simulate database authentication
        if ("admin".equals(context.getUsername()) && "password".equals(context.getPassword())) {
            Map<String, Object> metadata = new HashMap<>();
            metadata.put("source", "database");
            metadata.put("role", "administrator");
            return AuthenticationResult.success("admin_user_123", metadata);
        }
        return AuthenticationResult.failure("Invalid credentials");
    }

    @Override
    public boolean supports(AuthenticationContext context) {
        // Support all standard username/password authentication
        return context.getUsername() != null && context.getPassword() != null;
    }

    @Override
    public String getProviderName() {
        return "database";
    }

    @Override
    public int getConfidenceLevel() {
        return 50;
    }
}

public class LdapAuthenticationProvider implements AuthenticationProvider {
    @Override
    public AuthenticationResult authenticate(AuthenticationContext context) {
        // Simulate LDAP authentication
        if (context.getUsername().contains("@company.com")) {
            Map<String, Object> metadata = new HashMap<>();
            metadata.put("source", "ldap");
            metadata.put("domain", "company.com");
            return AuthenticationResult.success("ldap_user_456", metadata);
        }
        return AuthenticationResult.failure("LDAP authentication failed");
    }

    @Override
    public boolean supports(AuthenticationContext context) {
        // Support domain-based usernames
        return context.getUsername() != null && 
               context.getUsername().contains("@") &&
               context.getPassword() != null;
    }

    @Override
    public String getProviderName() {
        return "ldap";
    }

    @Override
    public int getConfidenceLevel() {
        return 80;
    }
}

public class ApiKeyAuthenticationProvider implements AuthenticationProvider {
    @Override
    public AuthenticationResult authenticate(AuthenticationContext context) {
        String apiKey = (String) context.getAttributes().get("api_key");
        if ("secret_api_key_123".equals(apiKey)) {
            Map<String, Object> metadata = new HashMap<>();
            metadata.put("source", "api_key");
            metadata.put("key_type", "service");
            return AuthenticationResult.success("api_user_789", metadata);
        }
        return AuthenticationResult.failure("Invalid API key");
    }

    @Override
    public boolean supports(AuthenticationContext context) {
        // Support API key authentication
        return context.getAttributes().containsKey("api_key");
    }

    @Override
    public String getProviderName() {
        return "api_key";
    }

    @Override
    public int getConfidenceLevel() {
        return 90;
    }
}
Enter fullscreen mode Exit fullscreen mode

The authentication service uses contextual resolution:


java
public class AuthenticationService {
    private final List<AuthenticationProvider> providers;

    public AuthenticationService() {
        this.providers = new ArrayList<>();
        loadProviders();
    }

    private void loadProviders() {
        ServiceLoader<AuthenticationProvider> loader = ServiceLoader.load(AuthenticationProvider.class);

        for (AuthenticationProvider provider : loader) {
            providers.add(provider);
            System.out.println("Loaded authentication provider: " + provider.getProviderName() + 
                             " (confidence: " + provider.getConfidenceLevel() + ")");
        }

        // Sort by confidence level (highest first)
        providers.sort((a, b) -> Integer.compare(b.getConfidenceLevel(), a.getConfidenceLevel()));
    }

    public AuthenticationResult authenticate(AuthenticationContext context) {
        List<AuthenticationProvider> supportedProviders = providers.stream()
            .filter(provider -> provider.supports(context))
            .collect(Collectors.toList());

        if (supportedProviders.isEmpty()) {
            return AuthenticationResult.failure("No suitable authentication provider found");
        }

        // Try providers in order of confidence
        for (AuthenticationProvider provider : supportedProviders) {
            try {
                System.out.println("Attempting authentication with provider: " + provider.getProviderName());
                AuthenticationResult result = provider.authenticate(context);

                if (result.isSuccess()) {
                    System.out.println("Authentication successful with provider: " +

---
## 101 Books

**101 Books** is an AI-driven publishing company co-founded by author **Aarav Joshi**. By leveraging advanced AI technology, we keep our publishing costs incredibly low—some books are priced as low as **$4**—making quality knowledge accessible to everyone.

Check out our book **[Golang Clean Code](https://www.amazon.com/dp/B0DQQF9K3Z)** available on Amazon. 

Stay tuned for updates and exciting news. When shopping for books, search for **Aarav Joshi** to find more of our titles. Use the provided link to enjoy **special discounts**!

## Our Creations

Be sure to check out our creations:

**[Investor Central](https://www.investorcentral.co.uk/)** | **[Investor Central Spanish](https://spanish.investorcentral.co.uk/)** | **[Investor Central German](https://german.investorcentral.co.uk/)** | **[Smart Living](https://smartliving.investorcentral.co.uk/)** | **[Epochs & Echoes](https://epochsandechoes.com/)** | **[Puzzling Mysteries](https://www.puzzlingmysteries.com/)** | **[Hindutva](http://hindutva.epochsandechoes.com/)** | **[Elite Dev](https://elitedev.in/)** | **[JS Schools](https://jsschools.com/)**

---

### We are on Medium

**[Tech Koala Insights](https://techkoalainsights.com/)** | **[Epochs & Echoes World](https://world.epochsandechoes.com/)** | **[Investor Central Medium](https://medium.investorcentral.co.uk/)** | **[Puzzling Mysteries Medium](https://medium.com/puzzling-mysteries)** | **[Science & Epochs Medium](https://science.epochsandechoes.com/)** | **[Modern Hindutva](https://modernhindutva.substack.com/)**

Enter fullscreen mode Exit fullscreen mode

Top comments (0)

OSZAR »