Provide environment variables for config options (closes #161)

Change-Id: Iacb5902f4206fbf0a7a5bd6979aae4fe7a3be914
diff --git a/Changes b/Changes
index 30d50bc..42d6bb0 100644
--- a/Changes
+++ b/Changes
@@ -1,3 +1,6 @@
+    - Support environment variables overriding configuration properties.
+      (closes #161)(diewald)
+
 0.3.3 2026-01-29
     - Introduce announcements (diewald)
 
diff --git a/Dockerfile b/Dockerfile
index 5da636d..7b41b18 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -23,7 +23,7 @@
 RUN addgroup -S korap && \
     adduser -S export -G korap && \
     mkdir export && \
-    chown -R export.korap /export
+    chown -R export:korap /export
 
 WORKDIR /export
 
diff --git a/README.md b/README.md
index bf4892c..ac8887e 100644
--- a/README.md
+++ b/README.md
@@ -129,9 +129,69 @@
 Alternatively a file named `exportPlugin.conf` can be stored in the
 same directory as the java jar.
 
+### Environment Variables
+
+Configuration can also be set via environment variables, which take precedence over
+configuration file values. This is particularly useful for Docker deployments and
+containerized environments.
+
+The priority order is (highest to lowest):
+1. Environment variables
+2. Custom configuration file (if provided as argument)
+3. Default configuration file (`exportPlugin.conf`)
+
+### Configuration Reference
+
+The following configuration options are available. Each can be set either in a
+configuration file (using the property name) or via an environment variable:
+
+#### Server Configuration
+
+- `server.port` or `KALAMAR_EXPORT_SERVER_PORT`: Port of the export server (default: `7777`)
+- `server.host` or `KALAMAR_EXPORT_SERVER_HOST`: Host address of the server (default: `localhost`)
+- `server.scheme` or `KALAMAR_EXPORT_SERVER_SCHEME`: URL scheme for the server (default: `https`)
+- `server.origin` or `KALAMAR_EXPORT_SERVER_ORIGIN`: CORS origin for SSE responses (default: `*`)
+
+#### API Configuration
+
+- `api.port` or `KALAMAR_EXPORT_API_PORT`: Port of the KorAP API backend (default: `443`)
+- `api.host` or `KALAMAR_EXPORT_API_HOST`: Host address of the KorAP API backend (default: `korap.ids-mannheim.de`)
+- `api.scheme` or `KALAMAR_EXPORT_API_SCHEME`: URL scheme for the API backend (default: `https`)
+- `api.source` or `KALAMAR_EXPORT_API_SOURCE`: Source string for exports, useful when running behind a proxy (optional)
+- `api.path` or `KALAMAR_EXPORT_API_PATH`: Additional path prefix for the API (default: empty)
+
+#### Asset Configuration
+
+- `asset.host` or `KALAMAR_EXPORT_ASSET_HOST`: Host address for assets/stylesheets (default: `korap.ids-mannheim.de`)
+- `asset.port` or `KALAMAR_EXPORT_ASSET_PORT`: Port for assets (default: empty, uses default port)
+- `asset.scheme` or `KALAMAR_EXPORT_ASSET_SCHEME`: URL scheme for assets (default: `https`)
+- `asset.path` or `KALAMAR_EXPORT_ASSET_PATH`: Path prefix for assets (default: empty)
+
+#### Export Configuration
+
+- `conf.page_size` or `KALAMAR_EXPORT_PAGE_SIZE`: Number of matches to fetch per API request (default: `5`)
+- `conf.max_exp_limit` or `KALAMAR_EXPORT_MAX_EXP_LIMIT`: Maximum number of matches allowed per export (default: `10000`)
+- `conf.file_dir` or `KALAMAR_EXPORT_FILE_DIR`: Directory for temporary export files (default: system temp directory)
+- `conf.default_hitc` or `KALAMAR_EXPORT_DEFAULT_HITC`: Default number of hits in the export form (default: `100`)
+
+#### Cookie Configuration
+
+- `cookie.name` or `KALAMAR_EXPORT_COOKIE_NAME`: Name of the Kalamar session cookie (default: `kalamar`)
+
+### Docker Example
+
+When running in Docker, you can set environment variables:
+
+```shell
+docker run -e KALAMAR_EXPORT_SERVER_PORT=8080 \
+           -e KALAMAR_EXPORT_API_HOST=api.example.com \
+           -e KALAMAR_EXPORT_MAX_EXP_LIMIT=50000 \
+           kalamar-export-plugin
+```
+
 ## License
 
-Copyright (c) 2020-2024, [IDS Mannheim](https://www.ids-mannheim.de/), Germany
+Copyright (c) 2020-2026, [IDS Mannheim](https://www.ids-mannheim.de/), Germany
 
 Kalamar-Plugin-Export is developed as part of the [KorAP](https://korap.ids-mannheim.de/)
 Corpus Analysis Platform at the Leibniz Institute for the German Language
diff --git a/src/main/java/de/ids_mannheim/korap/plkexport/ExWSConf.java b/src/main/java/de/ids_mannheim/korap/plkexport/ExWSConf.java
index d83c434..77801dc 100644
--- a/src/main/java/de/ids_mannheim/korap/plkexport/ExWSConf.java
+++ b/src/main/java/de/ids_mannheim/korap/plkexport/ExWSConf.java
@@ -10,7 +10,10 @@
 
 import java.io.*;
 import java.lang.String;
+import java.util.HashMap;
+import java.util.Map;
 import java.util.Properties;
+import java.util.function.Function;
 
 import org.tinylog.Logger;
 
@@ -21,6 +24,41 @@
 
     private static Properties prop;
 
+    // Environment provider function (can be overridden for testing)
+    private static Function<String, String> envProvider = System::getenv;
+
+    // Mapping from environment variable names to property names
+    private static final Map<String, String> ENV_TO_PROP = new HashMap<>();
+    static {
+        // Server configuration
+        ENV_TO_PROP.put("KALAMAR_EXPORT_SERVER_PORT", "server.port");
+        ENV_TO_PROP.put("KALAMAR_EXPORT_SERVER_HOST", "server.host");
+        ENV_TO_PROP.put("KALAMAR_EXPORT_SERVER_SCHEME", "server.scheme");
+        ENV_TO_PROP.put("KALAMAR_EXPORT_SERVER_ORIGIN", "server.origin");
+        
+        // API configuration
+        ENV_TO_PROP.put("KALAMAR_EXPORT_API_PORT", "api.port");
+        ENV_TO_PROP.put("KALAMAR_EXPORT_API_HOST", "api.host");
+        ENV_TO_PROP.put("KALAMAR_EXPORT_API_SCHEME", "api.scheme");
+        ENV_TO_PROP.put("KALAMAR_EXPORT_API_SOURCE", "api.source");
+        ENV_TO_PROP.put("KALAMAR_EXPORT_API_PATH", "api.path");
+        
+        // Asset configuration
+        ENV_TO_PROP.put("KALAMAR_EXPORT_ASSET_HOST", "asset.host");
+        ENV_TO_PROP.put("KALAMAR_EXPORT_ASSET_PORT", "asset.port");
+        ENV_TO_PROP.put("KALAMAR_EXPORT_ASSET_SCHEME", "asset.scheme");
+        ENV_TO_PROP.put("KALAMAR_EXPORT_ASSET_PATH", "asset.path");
+        
+        // General configuration
+        ENV_TO_PROP.put("KALAMAR_EXPORT_PAGE_SIZE", "conf.page_size");
+        ENV_TO_PROP.put("KALAMAR_EXPORT_MAX_EXP_LIMIT", "conf.max_exp_limit");
+        ENV_TO_PROP.put("KALAMAR_EXPORT_FILE_DIR", "conf.file_dir");
+        ENV_TO_PROP.put("KALAMAR_EXPORT_DEFAULT_HITC", "conf.default_hitc");
+        
+        // Cookie configuration
+        ENV_TO_PROP.put("KALAMAR_EXPORT_COOKIE_NAME", "cookie.name");
+    }
+
     /*
      * Returns version of the Export Plugin
      */
@@ -51,6 +89,20 @@
         prop = null;
     }
 
+    /*
+     * Sets a custom environment provider function.
+     * This is useful for testing environment variable overrides
+     * without actually setting system environment variables.
+     * Pass null to reset to the default System.getenv provider.
+     */
+    public static void setEnvironmentProvider(Function<String, String> provider) {
+        if (provider == null) {
+            envProvider = System::getenv;
+        } else {
+            envProvider = provider;
+        }
+    }
+
     /**
     *Loads properties from a UTF-8 encoded file
     */
@@ -94,6 +146,8 @@
     * Returns export properties 
     * The properties in exportPlugin.conf are the default properties
     * which can be overwritten by the properties in propFile.
+    * Environment variables have the highest priority and override both
+    * config file values.
     */
     public static Properties properties (String propFile) {
      
@@ -106,8 +160,39 @@
         if (propFile != null){
             loadProp(prop, propFile);
         }
+        
+        // Apply environment variable overrides
+        applyEnvironmentOverrides(prop);
     
         return prop;
     };
 
+    /*
+     * Apply environment variable overrides to the properties.
+     * Environment variables have the highest priority.
+     */
+    private static void applyEnvironmentOverrides(Properties props) {
+        for (Map.Entry<String, String> entry : ENV_TO_PROP.entrySet()) {
+            String envValue = getEnvironmentVariable(entry.getKey());
+            if (envValue != null && !envValue.isEmpty()) {
+                props.setProperty(entry.getValue(), envValue);
+            }
+        }
+    }
+
+    /*
+     * Get an environment variable value using the configured provider.
+     */
+    protected static String getEnvironmentVariable(String name) {
+        return envProvider.apply(name);
+    }
+
+    /*
+     * Returns the mapping of environment variable names to property names.
+     * Useful for documentation and testing.
+     */
+    public static Map<String, String> getEnvToPropertyMapping() {
+        return new HashMap<>(ENV_TO_PROP);
+    }
+
 }
diff --git a/src/test/java/de/ids_mannheim/korap/plkexport/ExWSConfEnvTest.java b/src/test/java/de/ids_mannheim/korap/plkexport/ExWSConfEnvTest.java
new file mode 100644
index 0000000..8df2693
--- /dev/null
+++ b/src/test/java/de/ids_mannheim/korap/plkexport/ExWSConfEnvTest.java
@@ -0,0 +1,443 @@
+package de.ids_mannheim.korap.plkexport;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNotEquals;
+
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Properties;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+
+/**
+ * Test suite for environment variable overrides in ExWSConf.
+ * Tests that environment variables can override configuration file values
+ * and have higher priority than config files.
+ */
+public class ExWSConfEnvTest {
+
+    private Map<String, String> mockEnv;
+
+    @Before
+    public void setUp() {
+        // Create a mock environment map for each test
+        mockEnv = new HashMap<>();
+        
+        // Clear any previously loaded properties
+        ExWSConf.clearProp();
+        
+        // Reset to default environment provider
+        ExWSConf.setEnvironmentProvider(null);
+    }
+
+    @After
+    public void tearDown() {
+        // Reset state after each test
+        ExWSConf.clearProp();
+        ExWSConf.setEnvironmentProvider(null);
+    }
+
+    /**
+     * Test that the environment-to-property mapping contains all expected entries
+     */
+    @Test
+    public void testEnvMappingContainsAllExpectedVariables() {
+        Map<String, String> mapping = ExWSConf.getEnvToPropertyMapping();
+        
+        // Server configuration
+        assertEquals("server.port", mapping.get("KALAMAR_EXPORT_SERVER_PORT"));
+        assertEquals("server.host", mapping.get("KALAMAR_EXPORT_SERVER_HOST"));
+        assertEquals("server.scheme", mapping.get("KALAMAR_EXPORT_SERVER_SCHEME"));
+        assertEquals("server.origin", mapping.get("KALAMAR_EXPORT_SERVER_ORIGIN"));
+        
+        // API configuration
+        assertEquals("api.port", mapping.get("KALAMAR_EXPORT_API_PORT"));
+        assertEquals("api.host", mapping.get("KALAMAR_EXPORT_API_HOST"));
+        assertEquals("api.scheme", mapping.get("KALAMAR_EXPORT_API_SCHEME"));
+        assertEquals("api.source", mapping.get("KALAMAR_EXPORT_API_SOURCE"));
+        assertEquals("api.path", mapping.get("KALAMAR_EXPORT_API_PATH"));
+        
+        // Asset configuration
+        assertEquals("asset.host", mapping.get("KALAMAR_EXPORT_ASSET_HOST"));
+        assertEquals("asset.port", mapping.get("KALAMAR_EXPORT_ASSET_PORT"));
+        assertEquals("asset.scheme", mapping.get("KALAMAR_EXPORT_ASSET_SCHEME"));
+        assertEquals("asset.path", mapping.get("KALAMAR_EXPORT_ASSET_PATH"));
+        
+        // General configuration
+        assertEquals("conf.page_size", mapping.get("KALAMAR_EXPORT_PAGE_SIZE"));
+        assertEquals("conf.max_exp_limit", mapping.get("KALAMAR_EXPORT_MAX_EXP_LIMIT"));
+        assertEquals("conf.file_dir", mapping.get("KALAMAR_EXPORT_FILE_DIR"));
+        assertEquals("conf.default_hitc", mapping.get("KALAMAR_EXPORT_DEFAULT_HITC"));
+        
+        // Cookie configuration
+        assertEquals("cookie.name", mapping.get("KALAMAR_EXPORT_COOKIE_NAME"));
+    }
+
+    /**
+     * Test that KALAMAR_EXPORT_SERVER_PORT environment variable overrides server.port
+     */
+    @Test
+    public void testServerPortEnvOverride() {
+        mockEnv.put("KALAMAR_EXPORT_SERVER_PORT", "9999");
+        ExWSConf.setEnvironmentProvider(mockEnv::get);
+        
+        Properties props = ExWSConf.properties(null);
+        assertEquals("9999", props.getProperty("server.port"));
+    }
+
+    /**
+     * Test that KALAMAR_EXPORT_SERVER_HOST environment variable overrides server.host
+     */
+    @Test
+    public void testServerHostEnvOverride() {
+        mockEnv.put("KALAMAR_EXPORT_SERVER_HOST", "custom.host.example.com");
+        ExWSConf.setEnvironmentProvider(mockEnv::get);
+        
+        Properties props = ExWSConf.properties(null);
+        assertEquals("custom.host.example.com", props.getProperty("server.host"));
+    }
+
+    /**
+     * Test that KALAMAR_EXPORT_SERVER_SCHEME environment variable overrides server.scheme
+     */
+    @Test
+    public void testServerSchemeEnvOverride() {
+        mockEnv.put("KALAMAR_EXPORT_SERVER_SCHEME", "http");
+        ExWSConf.setEnvironmentProvider(mockEnv::get);
+        
+        Properties props = ExWSConf.properties(null);
+        assertEquals("http", props.getProperty("server.scheme"));
+    }
+
+    /**
+     * Test that KALAMAR_EXPORT_API_PORT environment variable overrides api.port
+     */
+    @Test
+    public void testApiPortEnvOverride() {
+        mockEnv.put("KALAMAR_EXPORT_API_PORT", "8080");
+        ExWSConf.setEnvironmentProvider(mockEnv::get);
+        
+        Properties props = ExWSConf.properties(null);
+        assertEquals("8080", props.getProperty("api.port"));
+    }
+
+    /**
+     * Test that KALAMAR_EXPORT_API_HOST environment variable overrides api.host
+     */
+    @Test
+    public void testApiHostEnvOverride() {
+        mockEnv.put("KALAMAR_EXPORT_API_HOST", "api.custom.example.com");
+        ExWSConf.setEnvironmentProvider(mockEnv::get);
+        
+        Properties props = ExWSConf.properties(null);
+        assertEquals("api.custom.example.com", props.getProperty("api.host"));
+    }
+
+    /**
+     * Test that KALAMAR_EXPORT_API_SCHEME environment variable overrides api.scheme
+     */
+    @Test
+    public void testApiSchemeEnvOverride() {
+        mockEnv.put("KALAMAR_EXPORT_API_SCHEME", "http");
+        ExWSConf.setEnvironmentProvider(mockEnv::get);
+        
+        Properties props = ExWSConf.properties(null);
+        assertEquals("http", props.getProperty("api.scheme"));
+    }
+
+    /**
+     * Test that KALAMAR_EXPORT_API_SOURCE environment variable sets api.source
+     */
+    @Test
+    public void testApiSourceEnvOverride() {
+        mockEnv.put("KALAMAR_EXPORT_API_SOURCE", "custom-source.example.com");
+        ExWSConf.setEnvironmentProvider(mockEnv::get);
+        
+        Properties props = ExWSConf.properties(null);
+        assertEquals("custom-source.example.com", props.getProperty("api.source"));
+    }
+
+    /**
+     * Test that KALAMAR_EXPORT_API_PATH environment variable sets api.path
+     */
+    @Test
+    public void testApiPathEnvOverride() {
+        mockEnv.put("KALAMAR_EXPORT_API_PATH", "/custom/api/path");
+        ExWSConf.setEnvironmentProvider(mockEnv::get);
+        
+        Properties props = ExWSConf.properties(null);
+        assertEquals("/custom/api/path", props.getProperty("api.path"));
+    }
+
+    /**
+     * Test that KALAMAR_EXPORT_ASSET_HOST environment variable overrides asset.host
+     */
+    @Test
+    public void testAssetHostEnvOverride() {
+        mockEnv.put("KALAMAR_EXPORT_ASSET_HOST", "assets.custom.example.com");
+        ExWSConf.setEnvironmentProvider(mockEnv::get);
+        
+        Properties props = ExWSConf.properties(null);
+        assertEquals("assets.custom.example.com", props.getProperty("asset.host"));
+    }
+
+    /**
+     * Test that KALAMAR_EXPORT_ASSET_PORT environment variable sets asset.port
+     */
+    @Test
+    public void testAssetPortEnvOverride() {
+        mockEnv.put("KALAMAR_EXPORT_ASSET_PORT", "8888");
+        ExWSConf.setEnvironmentProvider(mockEnv::get);
+        
+        Properties props = ExWSConf.properties(null);
+        assertEquals("8888", props.getProperty("asset.port"));
+    }
+
+    /**
+     * Test that KALAMAR_EXPORT_ASSET_SCHEME environment variable overrides asset.scheme
+     */
+    @Test
+    public void testAssetSchemeEnvOverride() {
+        mockEnv.put("KALAMAR_EXPORT_ASSET_SCHEME", "http");
+        ExWSConf.setEnvironmentProvider(mockEnv::get);
+        
+        Properties props = ExWSConf.properties(null);
+        assertEquals("http", props.getProperty("asset.scheme"));
+    }
+
+    /**
+     * Test that KALAMAR_EXPORT_ASSET_PATH environment variable sets asset.path
+     */
+    @Test
+    public void testAssetPathEnvOverride() {
+        mockEnv.put("KALAMAR_EXPORT_ASSET_PATH", "/custom/asset/path");
+        ExWSConf.setEnvironmentProvider(mockEnv::get);
+        
+        Properties props = ExWSConf.properties(null);
+        assertEquals("/custom/asset/path", props.getProperty("asset.path"));
+    }
+
+    /**
+     * Test that KALAMAR_EXPORT_PAGE_SIZE environment variable overrides conf.page_size
+     */
+    @Test
+    public void testPageSizeEnvOverride() {
+        mockEnv.put("KALAMAR_EXPORT_PAGE_SIZE", "25");
+        ExWSConf.setEnvironmentProvider(mockEnv::get);
+        
+        Properties props = ExWSConf.properties(null);
+        assertEquals("25", props.getProperty("conf.page_size"));
+    }
+
+    /**
+     * Test that KALAMAR_EXPORT_MAX_EXP_LIMIT environment variable overrides conf.max_exp_limit
+     */
+    @Test
+    public void testMaxExpLimitEnvOverride() {
+        mockEnv.put("KALAMAR_EXPORT_MAX_EXP_LIMIT", "50000");
+        ExWSConf.setEnvironmentProvider(mockEnv::get);
+        
+        Properties props = ExWSConf.properties(null);
+        assertEquals("50000", props.getProperty("conf.max_exp_limit"));
+    }
+
+    /**
+     * Test that KALAMAR_EXPORT_FILE_DIR environment variable sets conf.file_dir
+     */
+    @Test
+    public void testFileDirEnvOverride() {
+        mockEnv.put("KALAMAR_EXPORT_FILE_DIR", "/custom/export/dir");
+        ExWSConf.setEnvironmentProvider(mockEnv::get);
+        
+        Properties props = ExWSConf.properties(null);
+        assertEquals("/custom/export/dir", props.getProperty("conf.file_dir"));
+    }
+
+    /**
+     * Test that KALAMAR_EXPORT_DEFAULT_HITC environment variable overrides conf.default_hitc
+     */
+    @Test
+    public void testDefaultHitcEnvOverride() {
+        mockEnv.put("KALAMAR_EXPORT_DEFAULT_HITC", "500");
+        ExWSConf.setEnvironmentProvider(mockEnv::get);
+        
+        Properties props = ExWSConf.properties(null);
+        assertEquals("500", props.getProperty("conf.default_hitc"));
+    }
+
+    /**
+     * Test that KALAMAR_EXPORT_SERVER_ORIGIN environment variable sets server.origin
+     */
+    @Test
+    public void testServerOriginEnvOverride() {
+        mockEnv.put("KALAMAR_EXPORT_SERVER_ORIGIN", "https://custom.origin.example.com");
+        ExWSConf.setEnvironmentProvider(mockEnv::get);
+        
+        Properties props = ExWSConf.properties(null);
+        assertEquals("https://custom.origin.example.com", props.getProperty("server.origin"));
+    }
+
+    /**
+     * Test that KALAMAR_EXPORT_COOKIE_NAME environment variable sets cookie.name
+     */
+    @Test
+    public void testCookieNameEnvOverride() {
+        mockEnv.put("KALAMAR_EXPORT_COOKIE_NAME", "custom_cookie_name");
+        ExWSConf.setEnvironmentProvider(mockEnv::get);
+        
+        Properties props = ExWSConf.properties(null);
+        assertEquals("custom_cookie_name", props.getProperty("cookie.name"));
+    }
+
+    /**
+     * Test that environment variables override values from configuration files
+     */
+    @Test
+    public void testEnvOverridesConfigFile() {
+        // First, get default config value
+        ExWSConf.clearProp();
+        ExWSConf.setEnvironmentProvider(name -> null);  // No env vars
+        Properties propsWithoutEnv = ExWSConf.properties(null);
+        String defaultPort = propsWithoutEnv.getProperty("server.port");
+        
+        // Now set environment variable to different value
+        ExWSConf.clearProp();
+        mockEnv.put("KALAMAR_EXPORT_SERVER_PORT", "12345");
+        ExWSConf.setEnvironmentProvider(mockEnv::get);
+        
+        Properties propsWithEnv = ExWSConf.properties(null);
+        String envPort = propsWithEnv.getProperty("server.port");
+        
+        assertEquals("12345", envPort);
+        assertNotEquals(defaultPort, envPort);
+    }
+
+    /**
+     * Test that environment variables also override values from additional config file
+     */
+    @Test
+    public void testEnvOverridesSecondaryConfigFile() {
+        // Set environment variable
+        mockEnv.put("KALAMAR_EXPORT_PAGE_SIZE", "999");
+        ExWSConf.setEnvironmentProvider(mockEnv::get);
+        
+        // Load with secondary config file that also sets page_size
+        Properties props = ExWSConf.properties("exportPluginSec.conf");
+        
+        // Environment should win
+        assertEquals("999", props.getProperty("conf.page_size"));
+    }
+
+    /**
+     * Test that empty environment variables do not override config values
+     */
+    @Test
+    public void testEmptyEnvDoesNotOverride() {
+        // Set empty environment variable
+        mockEnv.put("KALAMAR_EXPORT_SERVER_PORT", "");
+        ExWSConf.setEnvironmentProvider(mockEnv::get);
+        
+        Properties props = ExWSConf.properties(null);
+        
+        // Should use config file value, not empty string
+        assertNotEquals("", props.getProperty("server.port"));
+    }
+
+    /**
+     * Test that null environment variables do not override config values
+     */
+    @Test
+    public void testNullEnvDoesNotOverride() {
+        // Set up provider that returns null for all variables
+        ExWSConf.setEnvironmentProvider(name -> null);
+        
+        Properties props = ExWSConf.properties(null);
+        
+        // Should use config file values
+        assertEquals("1024", props.getProperty("server.port"));
+        assertEquals("localhost", props.getProperty("server.host"));
+    }
+
+    /**
+     * Test multiple environment variables being set at once
+     */
+    @Test
+    public void testMultipleEnvOverrides() {
+        mockEnv.put("KALAMAR_EXPORT_SERVER_PORT", "7777");
+        mockEnv.put("KALAMAR_EXPORT_SERVER_HOST", "multi.test.com");
+        mockEnv.put("KALAMAR_EXPORT_API_PORT", "8888");
+        mockEnv.put("KALAMAR_EXPORT_PAGE_SIZE", "50");
+        mockEnv.put("KALAMAR_EXPORT_MAX_EXP_LIMIT", "100000");
+        ExWSConf.setEnvironmentProvider(mockEnv::get);
+        
+        Properties props = ExWSConf.properties(null);
+        
+        assertEquals("7777", props.getProperty("server.port"));
+        assertEquals("multi.test.com", props.getProperty("server.host"));
+        assertEquals("8888", props.getProperty("api.port"));
+        assertEquals("50", props.getProperty("conf.page_size"));
+        assertEquals("100000", props.getProperty("conf.max_exp_limit"));
+    }
+
+    /**
+     * Test that properties not set via environment use config file values
+     */
+    @Test
+    public void testNonOverriddenPropertiesRetainConfigValues() {
+        // Only override one property
+        mockEnv.put("KALAMAR_EXPORT_SERVER_PORT", "9876");
+        ExWSConf.setEnvironmentProvider(mockEnv::get);
+        
+        Properties props = ExWSConf.properties(null);
+        
+        // Overridden property
+        assertEquals("9876", props.getProperty("server.port"));
+        
+        // Non-overridden properties should retain config file values
+        assertEquals("localhost", props.getProperty("server.host"));
+        assertEquals("https", props.getProperty("server.scheme"));
+    }
+
+    /**
+     * Test that environment variables work with numeric values as strings
+     */
+    @Test
+    public void testNumericEnvValues() {
+        mockEnv.put("KALAMAR_EXPORT_SERVER_PORT", "65535");
+        mockEnv.put("KALAMAR_EXPORT_PAGE_SIZE", "100");
+        mockEnv.put("KALAMAR_EXPORT_MAX_EXP_LIMIT", "1000000");
+        mockEnv.put("KALAMAR_EXPORT_DEFAULT_HITC", "250");
+        ExWSConf.setEnvironmentProvider(mockEnv::get);
+        
+        Properties props = ExWSConf.properties(null);
+        
+        // Verify these can be parsed as integers
+        assertEquals(65535, Integer.parseInt(props.getProperty("server.port")));
+        assertEquals(100, Integer.parseInt(props.getProperty("conf.page_size")));
+        assertEquals(1000000, Integer.parseInt(props.getProperty("conf.max_exp_limit")));
+        assertEquals(250, Integer.parseInt(props.getProperty("conf.default_hitc")));
+    }
+
+    /**
+     * Test resetting environment provider to default
+     */
+    @Test
+    public void testResetEnvironmentProvider() {
+        // Set mock provider
+        mockEnv.put("KALAMAR_EXPORT_SERVER_PORT", "1111");
+        ExWSConf.setEnvironmentProvider(mockEnv::get);
+        
+        Properties propsWithMock = ExWSConf.properties(null);
+        assertEquals("1111", propsWithMock.getProperty("server.port"));
+        
+        // Reset and reload
+        ExWSConf.clearProp();
+        ExWSConf.setEnvironmentProvider(null);  // Reset to System.getenv
+        
+        Properties propsReset = ExWSConf.properties(null);
+        // Should use config file value (assuming no actual env var is set)
+        assertEquals("1024", propsReset.getProperty("server.port"));
+    }
+}