← Back to list

Java Scenario Based Interview Question 5: How to Dynamically Reload Configuration Files in…

Introduction: Why This Matters

Raju Methuku · 2025-04-20 12:45 · 3 claps · 7.2 min read
#java-interview-questions #java-interview #code-kata #java-best-practices #system-design-interview
Open on Medium ↗

Java Scenario Based Interview Question 5: How to Dynamically Reload Configuration Files in Non-Spring Java Applications

Introduction: Why This Matters

Imagine you’re building a Java application that connects to a database. You’ve wisely put the database connection details in a configuration file rather than hardcoding them. But what happens when the database password changes? Or when you need to adjust some settings while your application is running?

In the old days, you’d have to:

  1. Stop your application
  2. Edit the config file
  3. Restart your application
  4. Cross your fingers hoping nothing breaks during the restart

That’s like having to turn off your car’s engine just to adjust the radio volume! There must be a better way.

In this guide, I’ll show you how to build a Java solution that automatically detects changes in your configuration file and reloads the new values — without restarting your application. This is a superpower that professional Java developers use every day!

What We’re Building: A Real-World Example

Let’s say we’re building a weather service application with these requirements:

  • Runs 24/7 to provide weather updates
  • Connects to multiple data sources
  • Has adjustable settings like update frequency and data thresholds
  • Needs to adapt to changes without downtime

Our solution will:

  • Watch a configuration file for changes
  • Automatically reload new values when detected
  • Keep working with old values if the new config has errors
  • Be efficient (no constant file reading)

Understanding the Problem Deeply

Why Simple Solutions Don’t Work

Naive Approach #1: Read the file every time you need a setting

String getDatabaseUrl() {
    // This is BAD - file I/O on every call is slow!
    Properties props = new Properties();
    props.load(new FileInputStream("config.properties"));
    return props.getProperty("db.url");
}

Naive Approach #2: Read once at startup and never update

// This is better but doesn't handle runtime changes
class Config {
    static final String DB_URL = // read from file at startup
}

The Goldilocks Solution

We need something that:

  1. Reads the file once at startup
  2. Watches for changes in the background
  3. Only reloads when needed
  4. Handles errors gracefully

Get the Full Code Implementation

Want to dive right into the code? The complete implementation of this dynamic configuration reloader — including error handling, thread safety, and file watcher logic — is available on GitHub:

🔗 **GitHub Repository: Dynamic Config Reloader**

Step-by-Step Implementation

Step 1: Setting Up Your Project

First, let’s add the necessary libraries to your pom.xml:

    <dependency>
        <groupId>com.typesafe</groupId>
        <artifactId>config</artifactId>
        <version>1.4.2</version>
    </dependency>

Step 2: Creating a Sample Config File

Create a file named test.conf in your project directory with this content:

app {
  name = "TestApp123"
  version = "1.0.0"
  enabled = true
  maxConnections = 10
}

This uses the HOCON format (Human-Optimized Config Object Notation), which is like JSON but more readable and flexible.

Step 3: Building the DynamicConfig Class

package com.raju.codekatas.configsync.config;

import com.raju.codekatas.configsync.watcher.ConfigFileWatcher;
import com.raju.codekatas.configsync.watcher.NioConfigFileWatcher;
import com.typesafe.config.Config;
import com.typesafe.config.ConfigFactory;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.Closeable;
import java.io.File;
import java.io.IOException;
import java.util.Objects;
import java.util.concurrent.atomic.AtomicReference;

public class DynamicConfig implements Closeable {
    private static final Logger logger = LoggerFactory.getLogger(DynamicConfig.class);

    private final File configFile;
    private final ConfigFileWatcher fileWatcher;
    private final AtomicReference<Config> cachedConfig = new AtomicReference<>();
    private volatile long lastModified;

    public DynamicConfig(File configFile, ConfigFileWatcher fileWatcher) {
        this.configFile = Objects.requireNonNull(configFile, "Config file must not be null");
        this.fileWatcher = Objects.requireNonNull(fileWatcher, "FileWatcher must not be null");
        this.cachedConfig.set(loadConfigOrDefault());
        this.lastModified = configFile.lastModified();
        this.fileWatcher.start(this::onFileChanged);
    }

    // Factory method for convenience
    public static DynamicConfig fromPath(String dir, String fileName) {
        File configFile = new File(dir, fileName);
        if (!configFile.exists()) {
            throw new IllegalArgumentException("Config file does not exist: " + configFile.getAbsolutePath());
        }
        return new DynamicConfig(configFile, new NioConfigFileWatcher(configFile));
    }

    public Config getConfig() {
        long currentLastModified = configFile.lastModified();
        if (currentLastModified > lastModified) {
            logger.info("Config file changed, reloading...");
            reloadConfig();
        }
        return cachedConfig.get();
    }

    private void reloadConfig() {
        Config newConfig = loadConfigOrDefault();
        cachedConfig.set(newConfig);
        lastModified = configFile.lastModified();
    }

    private Config loadConfigOrDefault() {
        try {
            Config config = ConfigFactory.parseFile(configFile).resolve();
            logger.info("Loaded config from {}", configFile.getAbsolutePath());
            return config;
        } catch (Exception e) {
            logger.error("Failed to load config, using last known good config", e);
            Config fallback = cachedConfig.get();
            return fallback != null ? fallback : ConfigFactory.empty();
        }
    }

    private void onFileChanged() {
        lastModified = 0; // Force reload on next access
    }

    @Override
    public void close() throws IOException {
        fileWatcher.close();
        logger.info("DynamicConfig closed.");
    }
}

What is DynamicConfig?

DynamicConfig is a Java class that:

  • Loads your configuration file (like .conf, .json, or .properties)
  • Watches the file for changes using a file watcher
  • Automatically reloads the config when the file changes
  • Always gives you the latest config when you call getConfig()

Step 4: Creating FileWatcher class

package com.raju.codekatas.configsync.watcher;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.File;
import java.io.IOException;
import java.nio.file.*;

public class NioConfigFileWatcher implements ConfigFileWatcher {
    private static final Logger logger = LoggerFactory.getLogger(NioConfigFileWatcher.class);

    private final File configFile;
    public Thread watcherThread;
    private volatile boolean running = true;

    public NioConfigFileWatcher(File configFile) {
        this.configFile = configFile;
    }

    @Override
    public void start(Runnable onChange) {
        watcherThread = new Thread(() -> {
            Path path = configFile.toPath().getParent();
            String fileName = configFile.getName();
            try (WatchService watchService = FileSystems.getDefault().newWatchService()) {
                path.register(watchService, StandardWatchEventKinds.ENTRY_MODIFY);
                while (running) {
                    WatchKey key = watchService.take();
                    for (WatchEvent<?> event : key.pollEvents()) {
                        Path changed = (Path) event.context();
                        if (changed.getFileName().toString().equals(fileName)) {
                            logger.info("Detected change in {}", fileName);
                            onChange.run();
                        }
                    }
                    key.reset();
                }
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
                logger.info("File watcher thread interrupted, shutting down.");
            } catch (IOException e) {
                logger.error("Error watching config file", e);
            }
        }, "ConfigFileWatcher");
        watcherThread.setDaemon(true);
        watcherThread.start();
    }

    @Override
    public void close() {
        running = false;
        if (watcherThread != null) {
            watcherThread.interrupt();
        }
        logger.info("ConfigFileWatcher stopped.");
    }
}

How Does the File Watcher Work?

The file watcher uses Java’s WatchService API to monitor a directory for changes. When you edit and save your config file, the watcher detects the change and tells DynamicConfig to reload the file.

Step 5: Using DynamicConfig in Your Application

package com.raju.codekatas.configsync;

import com.raju.codekatas.configsync.config.DynamicConfig;
import com.typesafe.config.Config;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.Scanner;

public class ConfigLoaderExample {

    private static final Logger logger = LoggerFactory.getLogger(ConfigLoaderExample.class);

    public static void main(String[] args) throws Exception {
        // Start DynamicConfig
        DynamicConfig dynamicConfig = DynamicConfig.fromPath("src/main/resources", "test.conf");
        logger.info("App is running. Edit test.conf and type 'show' to see updated values.");

        Scanner scanner = new Scanner(System.in);
        while (true) {
            logger.info("Type 'show' to display config, or 'exit' to quit: ");
            String input = scanner.nextLine();
            if ("exit".equalsIgnoreCase(input)) {
                break;
            } else if ("show".equalsIgnoreCase(input)) {
                Config config = dynamicConfig.getConfig();
                String name = config.getString("app.name");
                String version = config.getString("app.version");
                boolean enabled = config.getBoolean("app.enabled");
                int maxConnections = config.getInt("app.maxConnections");
                logger.info("Current Config:");
                logger.info("Name: {}", name);
                logger.info("Version: {}", version);
                logger.info("Enabled: {}", enabled);
                logger.info("Max Connections: {}", maxConnections);
            }
        }
        dynamicConfig.close();

    }
}

Step-by-Step Testing Instructions

1. Prepare Your Config File

  • Make sure you have a file named test.conf in src/main/resources with content like:
app {
  name = "TestApp"
  version = "1.0.0"
  enabled = true
  maxConnections = 10
}

2. Run the Program

  • Run the ConfigLoaderExample class from your IDE or command line.
  • You should see a log message: App is running. Edit test.conf and type 'show' to see updated values.

3. Display the Current Config

  • In the console, type show and press Enter.
  • The program will log the current config values, for example:
Loaded config from /Users/rmethuku/Raju/pocs/code-katas/src/main/resources/test.conf
App is running. Edit test.conf and type 'show' to see updated values.
Type 'show' to display config, or 'exit' to quit: 
Current Config:
Name: TestApp12
Version: 1.0.0
Enabled: true
Max Connections: 10
Type 'show' to display config, or 'exit' to quit: 

4. Edit the Config File While the App is Running

  • Open src/main/resources/test.conf in a text editor.
  • Change a value, for example:
app {
  name = "MyNewApp"
  version = "2.0.0"
  enabled = false
  maxConnections = 20
}
  • Save the file.

5. Fetch the Updated Config

  • Go back to your running program.
  • Type show again and press Enter.
  • The program will now log the updated values:
Current Config:
Name: MyNewApp
Version: 2.0.0
Enabled: false
Max Connections: 20

6. Exit the Program

  • Type exit and press Enter to stop the program.

What’s Happening Behind the Scenes?

  • DynamicConfig is watching your config file for changes.
  • When you save the file, the watcher detects the change and marks the config as “stale.”
  • The next time you call getConfig() (by typing show), it reloads the file and gives you the latest values.
  • You never need to restart your app to pick up config changes!

Advanced Topics

Handling Different File Formats

Our solution works with multiple formats out of the box thanks to Typesafe Config:

JSON Example (config.json):

{
  "app": {
    "name": "TestApp",
    "version": "1.0.0",
    "enabled": true,
    "maxConnections": 10
  }
}

Properties Example (config.properties):

app.name=TestApp
app.version=1.0.0
app.enabled=true
app.maxConnections=10

Performance Considerations

  • The file watcher uses OS-level notifications, so it’s very efficient
  • Configs are only reloaded when actually needed (lazy loading)
  • The AtomicReference ensures thread-safe access

Error Handling Strategies

Our implementation has several safety nets:

  1. Keeps last known good config if new one fails to load
  2. Logs errors for debugging
  3. Uses atomic references to prevent partial updates

Common Pitfalls and Solutions

Problem: Changes aren’t being detected

  • Solution: Make sure you’re saving the file properly. Some editors save to temporary files first.

Problem: Getting old values after update

  • Solution: The config is only reloaded when you call getCurrentConfig(). Make sure you're not caching values yourself.

Problem: Too many reloads when saving

  • Solution: Some editors trigger multiple save events. You can add a short delay before reloading.

Real-World Enhancements

For production use, you might want to add:

  • Metrics to track how often config changes
  • Notification system to alert when config changes
  • Validation rules for config values
  • Support for remote config files (HTTP/S3)

Conclusion

Congratulations! You’ve now built a professional-grade configuration system that:

  • Dynamically reloads changes
  • Handles errors gracefully
  • Works with multiple file formats
  • Is thread-safe and efficient

This is exactly the kind of feature that separates amateur projects from professional ones. With this in your toolkit, you’re ready to build more robust, maintainable Java applications.

Remember: Great software adapts to change without skipping a beat. Now your applications can too!

Next Steps

  1. Try adding support for environment variable overrides
  2. Experiment with different config file formats
  3. Extend the system to watch multiple config files

Coming Up Next: Dynamic Configuration Reload in Spring Boot!

Watch this space for my next post, where we’ll explore how to implement this same dynamic configuration reloading functionality in Spring Boot applications!

While our current Java implementation works great for standard applications, Spring Boot offers some powerful built-in features that can make this process even smoother. We’ll cover:

  • How Spring’s @RefreshScope works
  • Using Spring Cloud Config for centralized configuration
  • The magic of the /actuator/refresh endpoint
  • Comparing our custom solution with Spring’s approach

Which would you like me to focus on first? Let me know in the comments!

Pro Tip: Bookmark this post so you can compare both approaches once the Spring version is live. You’ll be amazed at how differently these two solutions tackle the same problem!

👋 Let’s Connect!

If you found this post insightful, here’s how you can help spread the knowledge:

👏 Clap if you enjoyed it — your claps motivate me to keep sharing more Java tricks and insights! 🔗 Share this post with your network so others can learn too. 💬 Ask your questions or share your experiences in the comments. Have you encountered this situation? Let’s discuss!

🚀 Follow me for more deep dives into Java, design patterns, and programming gotchas! Let’s learn and grow together. 🌟


메타데이터
post_id
60fb16b86df4
slug
java-scenario-based-interview-question-5-how-to-dynamically-reload-configuration-files-in-60fb16b86df4
url
https://medium.com/@narasimha4789/java-scenario-based-interview-question-5-how-to-dynamically-reload-configuration-files-in-60fb16b86df4
canonical_url
https://medium.com/@narasimha4789/java-scenario-based-interview-question-5-how-to-dynamically-reload-configuration-files-in-60fb16b86df4
author_url
https://medium.com/@narasimha4789
status
ok
fetched_at
2026-08-06 07:53:32