Repository: smallrye/smallrye-stork
Pull request #784 (2024-02-19 08:53:57)

Provide support for Spring Boot configurations

cescoffier: Can you squash before merging?

Patch:
diff --git a/bom/pom.xml b/bom/pom.xml
index 91e425ab..227b2551 100644
--- a/bom/pom.xml
+++ b/bom/pom.xml
@@ -136,8 +136,11 @@
                 <artifactId>stork-microprofile-config</artifactId>
                 <version>${project.version}</version>
             </dependency>
-
-
+            <dependency>
+                <groupId>io.smallrye.stork</groupId>
+                <artifactId>stork-spring-boot-config</artifactId>
+                <version>${project.version}</version>
+            </dependency>
             <dependency>
                 <groupId>io.smallrye.stork</groupId>
                 <artifactId>stork-test-utils</artifactId>
diff --git a/core/src/main/java/io/smallrye/stork/utils/StorkConfigUtils.java b/core/src/main/java/io/smallrye/stork/utils/StorkConfigUtils.java
new file mode 100644
index 00000000..83d5d61f
--- /dev/null
+++ b/core/src/main/java/io/smallrye/stork/utils/StorkConfigUtils.java
@@ -0,0 +1,173 @@
+package io.smallrye.stork.utils;
+
+import static io.smallrye.stork.Stork.STORK;
+
+import java.util.HashMap;
+import java.util.Map;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import io.smallrye.stork.Stork;
+import io.smallrye.stork.spi.config.SimpleServiceConfig;
+
+public class StorkConfigUtils {
+
+    private static final Logger log = LoggerFactory.getLogger(StorkConfigUtils.class);
+
+    private static final String CONFIG_PROPERTY_PART_EXPRESSION = "\".*\"|[^.]+";
+    private static final Pattern CONFIG_PROP_PART = Pattern.compile(CONFIG_PROPERTY_PART_EXPRESSION);
+
+    /**
+     * The load-balancer segment used in the configuration key.
+     */
+    public static final String LOAD_BALANCER = "load-balancer";
+    /**
+     * The load balancer type configuration key.
+     */
+    public static final String LOAD_BALANCER_EMBEDDED = "load-balancer.type";
+
+    /**
+     * The service-discovery segment used in the configuration key.
+     */
+    public static final String SERVICE_DISCOVERY = "service-discovery";
+    /**
+     * The service discovery type configuration key.
+     */
+    public static final String SERVICE_DISCOVERY_EMBEDDED = "service-discovery.type";
+
+    /**
+     * The service-registrar segment used in the configuration key.
+     */
+    public static final String SERVICE_REGISTRAR = "service-registrar";
+    /**
+     * The service discovery type configuration key.
+     */
+    public static final String SERVICE_REGISTRAR_EMBEDDED = "service-registrar.type";
+
+    /**
+     * Computes and stores service properties based on the provided property name and value.
+     *
+     * <p>
+     * This method processes a property name and value, extracting relevant information and storing it in a map
+     * based on the service name. The property name is expected to follow a specific pattern associated with SmallRye Stork,
+     * and it's used to determine the service name and property key. If the property name does not match the expected
+     * pattern, it will be logged as a warning.
+     *
+     * @param propertiesByServiceName A map containing service properties indexed by service name.
+     * @param propertyName The name of the property to compute.
+     * @param propertyValue The value of the property to store.
+     */
+    public static void computeServiceProperty(Map<String, Map<String, String>> propertiesByServiceName, String propertyName,
+            String propertyValue) {
+
+        Matcher matcher = CONFIG_PROP_PART.matcher(propertyName);
+
+        if (!matcher.find()) {
+            return;
+        }
+        if (STORK.equals(matcher.group())) {
+
+            // all registry and load balancing properties are of form
+            // stork.<service-name>.(load-balancer|service-discovery)...
+            // or stork."<service-name>".(load-balancer|service-discovery)...
+            if (!matcher.find()) {
+                log.warn("Potentially invalid property for SmallRye Stork: " + propertyName);
+            }
+            String serviceName = unwrapFromQuotes(matcher.group());
+
+            int serviceNameEndIdx = matcher.end();
+
+            if (!matcher.find()) {
+                log.warn("Potentially invalid property for SmallRye Stork: " + propertyName);
+            }
+
+            // serviceName can be in double quotes
+            Map<String, String> serviceProperties = propertiesByServiceName.computeIfAbsent(serviceName,
+                    ignored -> new HashMap<>());
+
+            String serviceProperty = propertyKey(propertyName.substring(serviceNameEndIdx));
+            serviceProperties.put(serviceProperty, propertyValue);
+        }
+
+    }
+
+    public static SimpleServiceConfig buildServiceConfig(Map.Entry<String, Map<String, String>> serviceEntry) {
+        SimpleServiceConfig.Builder builder = new SimpleServiceConfig.Builder();
+
+        Map<String, String> properties = serviceEntry.getValue();
+
+        String serviceName = serviceEntry.getKey();
+
+        String loadBalancerType = properties.get(LOAD_BALANCER);
+        if (loadBalancerType == null) {
+            loadBalancerType = properties.get(LOAD_BALANCER_EMBEDDED);
+        }
+        builder.setServiceName(serviceName);
+        if (loadBalancerType != null) {
+            SimpleServiceConfig.SimpleLoadBalancerConfig loadBalancerConfig = new SimpleServiceConfig.SimpleLoadBalancerConfig(
+                    loadBalancerType, propertiesForPrefix(LOAD_BALANCER, properties));
+
+            builder = builder.setLoadBalancer(loadBalancerConfig);
+        }
+
+        String serviceDiscoveryType = properties.get(SERVICE_DISCOVERY);
+        // for yaml it is more convenient to have stork.my-service.service-discovery.type, so let's support it too:
+        if (serviceDiscoveryType == null) {
+            serviceDiscoveryType = properties.get(SERVICE_DISCOVERY_EMBEDDED);
+        }
+        if (serviceDiscoveryType != null) {
+            SimpleServiceConfig.SimpleServiceDiscoveryConfig serviceDiscoveryConfig = new SimpleServiceConfig.SimpleServiceDiscoveryConfig(
+                    serviceDiscoveryType, propertiesForPrefix(SERVICE_DISCOVERY, properties));
+
+            builder = builder.setServiceDiscovery(serviceDiscoveryConfig);
+        }
+
+        String serviceRegistrarType = properties.get(SERVICE_REGISTRAR);
+        if (serviceRegistrarType == null) {
+            serviceRegistrarType = properties.get(SERVICE_REGISTRAR_EMBEDDED);
+        }
+        if (serviceRegistrarType != null) {
+            SimpleServiceConfig.SimpleServiceRegistrarConfig serviceRegistrarConfig = new SimpleServiceConfig.SimpleServiceRegistrarConfig(
+                    serviceRegistrarType, propertiesForPrefix(SERVICE_REGISTRAR, properties));
+
+            builder = builder.setServiceRegistrar(serviceRegistrarConfig);
+        }
+        return builder.build();
+    }
+
+    public static String unwrapFromQuotes(String text) {
+        if (text.length() < 2) {
+            return text;
+        }
+        if (text.charAt(0) == '"' && text.charAt(text.length() - 1) == '"') {
+            return text.substring(1, text.length() - 1);
+        } else {
+            return text;
+        }
+    }
+
+    public static String propertyKey(String text) {
+        if (!text.isEmpty() && text.charAt(0) == '.') {
+            return text.substring(1);
+        }
+        return text;
+    }
+
+    public static Map<String, String> propertiesForPrefix(String prefix, Map<String, String> original) {
+        prefix = prefix.endsWith(".") ? prefix : prefix + ".";
+        Map<String, String> result = new HashMap<>();
+
+        for (Map.Entry<String, String> entry : original.entrySet()) {
+            String propertyName = entry.getKey();
+            if (propertyName.startsWith(prefix)) {
+                String nameWithoutPrefix = propertyName.substring(prefix.length());
+                result.put(nameWithoutPrefix, entry.getValue());
+            }
+        }
+
+        return result;
+    }
+}
diff --git a/docs/docs/microprofile-config.md b/docs/docs/microprofile-config.md
new file mode 100644
index 00000000..60341b4b
--- /dev/null
+++ b/docs/docs/microprofile-config.md
@@ -0,0 +1,28 @@
+# Stork MicroProfile Config 
+
+Stork integrates with MicroProfile Configuration out of the box, enabling seamless access to configuration properties. 
+This documentation explains how Stork can retrieve configuration details from the MicroProfile Config file present in the classpath.
+Quarkus uses this approach for reading configuration details from the MicroProfile Config file located within the classpath.
+
+
+## Dependency setup
+
+To enable MicroProfile Config integration in Stork, you need to include the following dependency:
+
+```xml
+<dependency>
+    <groupId>io.smallrye.stork</groupId>
+    <artifactId>smallrye-stork-microprofile</artifactId>
+    <version>{{version.current}}</version>
+</dependency>
+```
+
+## Initializing Stork
+
+If your framework lacks a pre-configured Stork instance, you'll need to perform initialization:
+
+```java linenums="1"
+{{ insert('examples/InitializationExample.java') }}
+```
+Upon initialization, Stork scans for the `io.smallrye.stork.config.MicroProfileConfigProvider` SPI provider and CDI beans (from version 2.x onwards). It then builds a comprehensive list of managed services by parsing the properties configuration files.
+
diff --git a/docs/docs/springboot-config.md b/docs/docs/springboot-config.md
new file mode 100644
index 00000000..199466a9
--- /dev/null
+++ b/docs/docs/springboot-config.md
@@ -0,0 +1,38 @@
+# Stork Spring Boot Config 
+
+Stork seamlessly supports Spring Boot configuration, facilitating access to configuration properties.
+This documentation elaborates on how Spring Boot developers can use Stork in their Spring Boot applications and configure it using the application.properties file.
+
+
+## Dependency setup
+
+To enable Spring Boot configuration integration in Stork, you need to include the following dependency:
+
+```xml
+<dependency>
+    <groupId>io.smallrye.stork</groupId>
+    <artifactId>stork-spring-boot-config</artifactId>
+    <version>{{version.current}}</version>
+</dependency>
+```
+
+## Initializing Stork
+
+Since Spring Boot lacks a pre-configured Stork instance, you'll need create one. It can be done by providing a Spring bean performing Stork initialization:
+
+```java linenums="1"
+{{ insert('examples/SpringBootInitializationExample.java') }}
+```
+Upon initialization, Stork scans for the `io.smallrye.stork.springboot.SpringBootConfigProvider` SPI provider and CDI beans (from version 2.x onwards). 
+It then builds a comprehensive list of managed services by parsing the properties configuration files.
+
+
+Please note the importance of the `io.smallrye.stork.springboot.SpringBootApplicationContextProvider` bean in our setup. 
+This bean has a critical role by granting Stork access to the current `org.springframework.context.ApplicationContext`.
+It enables it to retrieve configuration details effectively. Consequently, it's imperative that this bean is instantiated prior to initiating the Stork initialization process.
+In this case, we utilize the `@DependsOn` annotation for that.
+It allows us controlling the bean creation order.
+
+## Comprehensive Example
+
+You can check our [Guitar Hero Application](https://github.com/aureamunoz/spring-stork-guitar-hero/) showcasing the seamless integration of Stork with Spring Boot Configuration. 
diff --git a/docs/mkdocs.yml b/docs/mkdocs.yml
index 837ae2d0..5cc016d2 100644
--- a/docs/mkdocs.yml
+++ b/docs/mkdocs.yml
@@ -8,6 +8,8 @@ nav:
   - Overview: 'index.md'
   - Concepts: 'concepts.md'
   - Programmatic API: 'programmatic-api.md'
+  - Microprofile Configuration: 'microprofile-config.md'
+  - Spring Boot Configuration: 'springboot-config.md'
   - Observability: 'observability.md'
   - Javadoc: 'https://javadoc.io/doc/io.smallrye.stork/smallrye-stork-api/latest/index.html'
   - Using Stork with Quarkus: './quarkus.md'
diff --git a/docs/pom.xml b/docs/pom.xml
index da2df420..e3e17cb5 100644
--- a/docs/pom.xml
+++ b/docs/pom.xml
@@ -78,6 +78,11 @@
             <groupId>io.smallrye.stork</groupId>
             <artifactId>stork-service-discovery-static-list</artifactId>
         </dependency>
+        <dependency>
+            <groupId>io.smallrye.stork</groupId>
+            <artifactId>stork-spring-boot-config</artifactId>
+            <scope>provided</scope>
+        </dependency>
         <dependency>
             <groupId>org.eclipse.microprofile.rest.client</groupId>
             <artifactId>microprofile-rest-client-api</artifactId>
diff --git a/docs/snippets/examples/SpringBootInitializationExample.java b/docs/snippets/examples/SpringBootInitializationExample.java
new file mode 100644
index 00000000..d2124258
--- /dev/null
+++ b/docs/snippets/examples/SpringBootInitializationExample.java
@@ -0,0 +1,29 @@
+package examples;
+
+import io.smallrye.stork.Stork;
+import io.smallrye.stork.springboot.SpringBootApplicationContextProvider;
+import org.springframework.boot.SpringApplication;
+import org.springframework.boot.autoconfigure.SpringBootApplication;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.DependsOn;
+
+@SpringBootApplication
+public class SpringBootInitializationExample {
+
+    public static void main(String[] args) {
+        SpringApplication.run(SpringBootInitializationExample.class, args);
+    }
+
+    @Bean
+    @DependsOn("springBootApplicationContextProvider")
+    public Stork stork() {
+        Stork.initialize();
+        Stork stork = Stork.getInstance();
+        return stork;
+    }
+
+    @Bean
+    public SpringBootApplicationContextProvider springBootApplicationContextProvider() {
+        return new SpringBootApplicationContextProvider();
+    }
+}
diff --git a/microprofile/revapi.json b/microprofile/revapi.json
index 0f405aec..014885bd 100644
--- a/microprofile/revapi.json
+++ b/microprofile/revapi.json
@@ -24,7 +24,44 @@
     "criticality" : "highlight",
     "minSeverity" : "POTENTIALLY_BREAKING",
     "minCriticality" : "documented",
-    "differences" : [ ]
+    "differences" : [
+      {
+        "ignore": true,
+        "code": "java.field.removedWithConstant",
+        "old": "field io.smallrye.stork.microprofile.MicroProfileConfigProvider.LOAD_BALANCER",
+        "justification": "Moved to stork-core io.smallrye.stork.utils.StorkConfigUtils.LOAD_BALANCER"
+      },
+      {
+        "ignore": true,
+        "code": "java.field.removedWithConstant",
+        "old": "field io.smallrye.stork.microprofile.MicroProfileConfigProvider.LOAD_BALANCER_EMBEDDED",
+        "justification": "Moved to stork-core io.smallrye.stork.utils.StorkConfigUtils.LOAD_BALANCER_EMBEDDED"
+      },
+      {
+        "ignore": true,
+        "code": "java.field.removedWithConstant",
+        "old": "field io.smallrye.stork.microprofile.MicroProfileConfigProvider.SERVICE_DISCOVERY",
+        "justification": "Moved to stork-core io.smallrye.stork.utils.StorkConfigUtils.SERVICE_DISCOVERY"
+      },
+      {
+        "ignore": true,
+        "code": "java.field.removedWithConstant",
+        "old": "field io.smallrye.stork.microprofile.MicroProfileConfigProvider.SERVICE_DISCOVERY_EMBEDDED",
+        "justification": "Moved to stork-core io.smallrye.stork.utils.StorkConfigUtils.SERVICE_DISCOVERY_EMBEDDED"
+      },
+      {
+        "ignore": true,
+        "code": "java.field.removedWithConstant",
+        "old": "field io.smallrye.stork.microprofile.MicroProfileConfigProvider.SERVICE_REGISTRAR",
+        "justification": "Moved to stork-core io.smallrye.stork.utils.StorkConfigUtils.SERVICE_REGISTRAR"
+      },
+      {
+        "ignore": true,
+        "code": "java.field.removedWithConstant",
+        "old": "field io.smallrye.stork.microprofile.MicroProfileConfigProvider.SERVICE_REGISTRAR_EMBEDDED",
+        "justification": "Moved to stork-core io.smallrye.stork.utils.StorkConfigUtils.SERVICE_REGISTRAR_EMBEDDED"
+      }
+    ]
   }
 }, {
   "extension" : "revapi.reporter.json",
diff --git a/microprofile/src/main/java/io/smallrye/stork/microprofile/MicroProfileConfigProvider.java b/microprofile/src/main/java/io/smallrye/stork/microprofile/MicroProfileConfigProvider.java
index 924e3301..cd6e30cd 100644
--- a/microprofile/src/main/java/io/smallrye/stork/microprofile/MicroProfileConfigProvider.java
+++ b/microprofile/src/main/java/io/smallrye/stork/microprofile/MicroProfileConfigProvider.java
@@ -1,57 +1,25 @@
 package io.smallrye.stork.microprofile;
 
-import static io.smallrye.stork.Stork.STORK;
-
 import java.util.ArrayList;
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
-import java.util.regex.Matcher;
-import java.util.regex.Pattern;
 
 import org.eclipse.microprofile.config.Config;
-import org.jboss.logging.Logger;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
 
 import io.smallrye.stork.api.config.ServiceConfig;
 import io.smallrye.stork.spi.config.ConfigProvider;
 import io.smallrye.stork.spi.config.SimpleServiceConfig;
+import io.smallrye.stork.utils.StorkConfigUtils;
 
 /**
  * Implementation of {@link ConfigProvider} using MicroProfile Config.
  */
 public class MicroProfileConfigProvider implements ConfigProvider {
 
-    private static final Logger log = Logger.getLogger(MicroProfileConfigProvider.class);
-
-    private static final String CONFIG_PROPERTY_PART_EXPRESSION = "\".*\"|[^.]+";
-    private static final Pattern CONFIG_PROP_PART = Pattern.compile(CONFIG_PROPERTY_PART_EXPRESSION);
-
-    /**
-     * The load-balancer segment used in the configuration key.
-     */
-    public static final String LOAD_BALANCER = "load-balancer";
-    /**
-     * The load balancer type configuration key.
-     */
-    public static final String LOAD_BALANCER_EMBEDDED = "load-balancer.type";
-
-    /**
-     * The service-discovery segment used in the configuration key.
-     */
-    public static final String SERVICE_DISCOVERY = "service-discovery";
-    /**
-     * The service discovery type configuration key.
-     */
-    public static final String SERVICE_DISCOVERY_EMBEDDED = "service-discovery.type";
-
-    /**
-     * The service-registrar segment used in the configuration key.
-     */
-    public static final String SERVICE_REGISTRAR = "service-registrar";
-    /**
-     * The service discovery type configuration key.
-     */
-    public static final String SERVICE_REGISTRAR_EMBEDDED = "service-registrar.type";
+    private static final Logger log = LoggerFactory.getLogger(MicroProfileConfigProvider.class);
 
     private final List<ServiceConfig> serviceConfigs = new ArrayList<>();
 
@@ -62,124 +30,18 @@ public class MicroProfileConfigProvider implements ConfigProvider {
         Config config = org.eclipse.microprofile.config.ConfigProvider.getConfig();
 
         Map<String, Map<String, String>> propertiesByServiceName = new HashMap<>();
-        Map<String, Map<String, String>> propertiesByRegistrarName = new HashMap<>();
 
         for (String propertyName : config.getPropertyNames()) {
-
-            Matcher matcher = CONFIG_PROP_PART.matcher(propertyName);
-
-            if (!matcher.find()) {
-                continue;
-            }
-            if (STORK.equals(matcher.group())) {
-
-                // all registry and load balancing properties are of form
-                // stork.<service-name>.(load-balancer|service-discovery)...
-                // or stork."<service-name>".(load-balancer|service-discovery)...
-                if (!matcher.find()) {
-                    log.warn("Potentially invalid property for SmallRye Stork: " + propertyName);
-                }
-                String serviceName = unwrapFromQuotes(matcher.group());
-
-                int serviceNameEndIdx = matcher.end();
-
-                if (!matcher.find()) {
-                    log.warn("Potentially invalid property for SmallRye Stork: " + propertyName);
-                }
-
-                // serviceName can be in double quotes
-                Map<String, String> serviceProperties = propertiesByServiceName.computeIfAbsent(serviceName,
-                        ignored -> new HashMap<>());
-
-                String serviceProperty = propertyKey(propertyName.substring(serviceNameEndIdx));
-                serviceProperties.put(serviceProperty,
-                        config.getValue(propertyName, String.class));
-            }
+            StorkConfigUtils.computeServiceProperty(propertiesByServiceName, propertyName,
+                    config.getValue(propertyName, String.class));
         }
 
         for (Map.Entry<String, Map<String, String>> serviceEntry : propertiesByServiceName.entrySet()) {
-            SimpleServiceConfig serviceConfig = buildServiceConfig(serviceEntry);
+            SimpleServiceConfig serviceConfig = StorkConfigUtils.buildServiceConfig(serviceEntry);
             serviceConfigs.add(serviceConfig);
         }
     }
 
-    private SimpleServiceConfig buildServiceConfig(Map.Entry<String, Map<String, String>> serviceEntry) {
-        SimpleServiceConfig.Builder builder = new SimpleServiceConfig.Builder();
-
-        Map<String, String> properties = serviceEntry.getValue();
-
-        String serviceName = serviceEntry.getKey();
-
-        String loadBalancerType = properties.get(LOAD_BALANCER);
-        if (loadBalancerType == null) {
-            loadBalancerType = properties.get(LOAD_BALANCER_EMBEDDED);
-        }
-        builder.setServiceName(serviceName);
-        if (loadBalancerType != null) {
-            SimpleServiceConfig.SimpleLoadBalancerConfig loadBalancerConfig = new SimpleServiceConfig.SimpleLoadBalancerConfig(
-                    loadBalancerType, propertiesForPrefix(LOAD_BALANCER, properties));
-
-            builder = builder.setLoadBalancer(loadBalancerConfig);
-        }
-
-        String serviceDiscoveryType = properties.get(SERVICE_DISCOVERY);
-        // for yaml it is more convenient to have stork.my-service.service-discovery.type, so let's support it too:
-        if (serviceDiscoveryType == null) {
-            serviceDiscoveryType = properties.get(SERVICE_DISCOVERY_EMBEDDED);
-        }
-        if (serviceDiscoveryType != null) {
-            SimpleServiceConfig.SimpleServiceDiscoveryConfig serviceDiscoveryConfig = new SimpleServiceConfig.SimpleServiceDiscoveryConfig(
-                    serviceDiscoveryType, propertiesForPrefix(SERVICE_DISCOVERY, properties));
-
-            builder = builder.setServiceDiscovery(serviceDiscoveryConfig);
-        }
-
-        String serviceRegistrarType = properties.get(SERVICE_REGISTRAR);
-        if (serviceRegistrarType == null) {
-            serviceRegistrarType = properties.get(SERVICE_REGISTRAR_EMBEDDED);
-        }
-        if (serviceRegistrarType != null) {
-            SimpleServiceConfig.SimpleServiceRegistrarConfig serviceRegistrarConfig = new SimpleServiceConfig.SimpleServiceRegistrarConfig(
-                    serviceRegistrarType, propertiesForPrefix(SERVICE_REGISTRAR, properties));
-
-            builder = builder.setServiceRegistrar(serviceRegistrarConfig);
-        }
-        return builder.build();
-    }
-
-    private String unwrapFromQuotes(String text) {
-        if (text.length() < 2) {
-            return text;
-        }
-        if (text.charAt(0) == '"' && text.charAt(text.length() - 1) == '"') {
-            return text.substring(1, text.length() - 1);
-        } else {
-            return text;
-        }
-    }
-
-    private String propertyKey(String text) {
-        if (!text.isEmpty() && text.charAt(0) == '.') {
-            return text.substring(1);
-        }
-        return text;
-    }
-
-    private Map<String, String> propertiesForPrefix(String prefix, Map<String, String> original) {
-        prefix = prefix.endsWith(".") ? prefix : prefix + ".";
-        Map<String, String> result = new HashMap<>();
-
-        for (Map.Entry<String, String> entry : original.entrySet()) {
-            String propertyName = entry.getKey();
-            if (propertyName.startsWith(prefix)) {
-                String nameWithoutPrefix = propertyName.substring(prefix.length());
-                result.put(nameWithoutPrefix, entry.getValue());
-            }
-        }
-
-        return result;
-    }
-
     @Override
     public List<ServiceConfig> getConfigs() {
         return serviceConfigs;
diff --git a/pom.xml b/pom.xml
index 8a1a65a7..20405bf0 100644
--- a/pom.xml
+++ b/pom.xml
@@ -51,6 +51,7 @@
         <version.mockito>5.2.0</version.mockito>
         <version.jackson>2.16.1</version.jackson>
         <version.weld>5.1.2.Final</version.weld>
+        <version.spring-boot>2.7.9</version.spring-boot>
 
         <revapi-maven-plugin.version>0.15.0</revapi-maven-plugin.version>
         <revapi-java.version>0.28.1</revapi-java.version>
@@ -207,6 +208,16 @@
                 <version>4.0.2.Final</version>
                 <scope>test</scope>
             </dependency>
+            <dependency>
+                <groupId>org.springframework.boot</groupId>
+                <artifactId>spring-boot</artifactId>
+                <version>${version.spring-boot}</version>
+            </dependency>
+            <dependency>
+                <groupId>org.springframework.boot</groupId>
+                <artifactId>spring-boot-autoconfigure</artifactId>
+                <version>${version.spring-boot}</version>
+            </dependency>
 
             <!-- Dependencies provided by the project -->
             <dependency>
@@ -358,6 +369,7 @@
         <module>bom</module>
         <module>core</module>
         <module>microprofile</module>
+        <module>spring-boot</module>
         <module>service-discovery/static-list</module>
         <module>service-discovery/consul</module>
         <module>service-discovery/dns</module>
diff --git a/spring-boot/pom.xml b/spring-boot/pom.xml
new file mode 100644
index 00000000..243373cf
--- /dev/null
+++ b/spring-boot/pom.xml
@@ -0,0 +1,57 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+    <modelVersion>4.0.0</modelVersion>
+
+    <parent>
+        <groupId>io.smallrye.stork</groupId>
+        <artifactId>stork-parent</artifactId>
+        <version>2.6.0-SNAPSHOT</version>
+    </parent>
+
+    <artifactId>stork-spring-boot-config</artifactId>
+
+    <name>SmallRye Stork : Spring Boot Config integration</name>
+
+    <properties>
+        <revapi.skip>false</revapi.skip>
+    </properties>
+
+    <dependencies>
+        <dependency>
+            <groupId>io.smallrye.stork</groupId>
+            <artifactId>stork-core</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-autoconfigure</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.jboss.logging</groupId>
+            <artifactId>jboss-logging</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>io.smallrye.config</groupId>
+            <artifactId>smallrye-config</artifactId>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>io.smallrye.stork</groupId>
+            <artifactId>stork-test-utils</artifactId>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.junit.jupiter</groupId>
+            <artifactId>junit-jupiter</artifactId>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.assertj</groupId>
+            <artifactId>assertj-core</artifactId>
+            <scope>test</scope>
+        </dependency>
+    </dependencies>
+</project>
diff --git a/spring-boot/revapi.json b/spring-boot/revapi.json
new file mode 100644
index 00000000..5bada53e
--- /dev/null
+++ b/spring-boot/revapi.json
@@ -0,0 +1,46 @@
+[ {
+  "extension" : "revapi.java",
+  "id" : "java",
+  "configuration" : {
+    "missing-classes" : {
+      "behavior" : "report",
+      "ignoreMissingAnnotations" : false
+    }
+  }
+}, {
+  "extension" : "revapi.filter",
+  "configuration" : {
+    "elements" : {
+      "include" : [ {
+        "matcher" : "java-package",
+        "match" : "/io\\.smallrye\\.stork\\.springboot(\\..*)?/"
+      } ]
+    }
+  }
+}, {
+  "extension" : "revapi.differences",
+  "id" : "breaking-changes",
+  "configuration" : {
+    "criticality" : "highlight",
+    "minSeverity" : "POTENTIALLY_BREAKING",
+    "minCriticality" : "documented",
+    "differences" : [ ]
+  }
+}, {
+  "extension" : "revapi.reporter.json",
+  "configuration" : {
+    "minSeverity" : "POTENTIALLY_BREAKING",
+    "minCriticality" : "documented",
+    "output" : "target/compatibility.json",
+    "indent" : true,
+    "append" : false,
+    "keepEmptyFile" : true
+  }
+}, {
+  "extension" : "revapi.reporter.text",
+  "configuration" : {
+    "minSeverity" : "POTENTIALLY_BREAKING",
+    "minCriticality" : "documented",
+    "output" : "out"
+  }
+} ]
\ No newline at end of file
diff --git a/spring-boot/src/main/java/io/smallrye/stork/springboot/SpringBootApplicationContextProvider.java b/spring-boot/src/main/java/io/smallrye/stork/springboot/SpringBootApplicationContextProvider.java
new file mode 100644
index 00000000..df3cf6c1
--- /dev/null
+++ b/spring-boot/src/main/java/io/smallrye/stork/springboot/SpringBootApplicationContextProvider.java
@@ -0,0 +1,18 @@
+package io.smallrye.stork.springboot;
+
+import org.springframework.beans.BeansException;
+import org.springframework.context.ApplicationContext;
+import org.springframework.context.ApplicationContextAware;
+
+public class SpringBootApplicationContextProvider implements ApplicationContextAware {
+    private static ApplicationContext applicationContext;
+
+    @Override
+    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
+        SpringBootApplicationContextProvider.applicationContext = applicationContext;
+    }
+
+    public static ApplicationContext getApplicationContext() {
+        return applicationContext;
+    }
+}
diff --git a/spring-boot/src/main/java/io/smallrye/stork/springboot/SpringBootConfigProvider.java b/spring-boot/src/main/java/io/smallrye/stork/springboot/SpringBootConfigProvider.java
new file mode 100644
index 00000000..f4558b41
--- /dev/null
+++ b/spring-boot/src/main/java/io/smallrye/stork/springboot/SpringBootConfigProvider.java
@@ -0,0 +1,67 @@
+package io.smallrye.stork.springboot;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import org.springframework.context.ApplicationContext;
+import org.springframework.core.env.ConfigurableEnvironment;
+import org.springframework.core.env.EnumerablePropertySource;
+import org.springframework.core.env.PropertySource;
+
+import io.smallrye.stork.api.config.ServiceConfig;
+import io.smallrye.stork.spi.config.ConfigProvider;
+import io.smallrye.stork.spi.config.SimpleServiceConfig;
+import io.smallrye.stork.utils.StorkConfigUtils;
+
+public class SpringBootConfigProvider implements ConfigProvider {
+
+    private final List<ServiceConfig> serviceConfigs = new ArrayList<>();
+
+    public SpringBootConfigProvider() {
+        ApplicationContext context = SpringBootApplicationContextProvider.getApplicationContext();
+        ConfigurableEnvironment environment = (ConfigurableEnvironment) context.getEnvironment();
+
+        Map<String, Map<String, String>> propertiesByServiceName = new HashMap<>();
+
+        for (String propertyName : getPropertyNames(environment)) {
+
+            StorkConfigUtils.computeServiceProperty(propertiesByServiceName, propertyName,
+                    environment.getProperty(propertyName));
+
+        }
+
+        for (Map.Entry<String, Map<String, String>> serviceEntry : propertiesByServiceName.entrySet()) {
+            SimpleServiceConfig serviceConfig = StorkConfigUtils.buildServiceConfig(serviceEntry);
+            serviceConfigs.add(serviceConfig);
+        }
+    }
+
+    private List<String> getPropertyNames(ConfigurableEnvironment environment) {
+        List<String> propertyNames = new ArrayList<>();
+        for (PropertySource<?> propertySource : environment.getPropertySources()) {
+            if (propertySource instanceof EnumerablePropertySource) {
+                String[] names = ((EnumerablePropertySource<?>) propertySource)
+                        .getPropertyNames();
+                if (names != null) {
+                    for (String name : names) {
+                        propertyNames.add(name);
+                    }
+                }
+            }
+        }
+        return propertyNames;
+    }
+
+    @Override
+    public List<ServiceConfig> getConfigs() {
+        return serviceConfigs;
+    }
+
+    @Override
+    public int priority() {
+        return 100;
+    }
+
+}
diff --git a/spring-boot/src/main/resources/META-INF/services/io.smallrye.stork.spi.config.ConfigProvider b/spring-boot/src/main/resources/META-INF/services/io.smallrye.stork.spi.config.ConfigProvider
new file mode 100644
index 00000000..0dab1aaa
--- /dev/null
+++ b/spring-boot/src/main/resources/META-INF/services/io.smallrye.stork.spi.config.ConfigProvider
@@ -0,0 +1,1 @@
+io.smallrye.stork.springboot.SpringBootConfigProvider
\ No newline at end of file


Test patch:
diff --git a/core/src/test/java/io/smallrye/stork/utils/StorkConfigUtilsTest.java b/core/src/test/java/io/smallrye/stork/utils/StorkConfigUtilsTest.java
new file mode 100644
index 00000000..b6342821
--- /dev/null
+++ b/core/src/test/java/io/smallrye/stork/utils/StorkConfigUtilsTest.java
@@ -0,0 +1,87 @@
+package io.smallrye.stork.utils;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.junit.jupiter.api.Assertions.*;
+import static org.mockito.Mockito.*;
+
+import java.util.HashMap;
+import java.util.Map;
+import java.util.logging.Logger;
+
+import org.junit.jupiter.api.Test;
+import org.mockito.Mock;
+
+public class StorkConfigUtilsTest {
+
+    @Mock
+    private Logger log;
+
+    @Test
+    void computeServiceProperty_ValidProperty() {
+        // Given
+        Map<String, Map<String, String>> propertiesByServiceName = new HashMap<>();
+        String propertyName = "stork.\"service-name\".load-balancer.property";
+        String propertyValue = "value";
+
+        // When
+        StorkConfigUtils.computeServiceProperty(propertiesByServiceName, propertyName, propertyValue);
+
+        // Then
+        assertTrue(propertiesByServiceName.containsKey("service-name"));
+        Map<String, String> serviceProperties = propertiesByServiceName.get("service-name");
+        assertEquals("value", serviceProperties.get("load-balancer.property"));
+    }
+
+    @Test
+    void computeServiceProperty_AnotherValidProperty() {
+        // Given
+        Map<String, Map<String, String>> propertiesByServiceName = new HashMap<>();
+        String propertyName = "stork.service-name.load-balancer.property";
+        String propertyValue = "value";
+
+        // When
+        StorkConfigUtils.computeServiceProperty(propertiesByServiceName, propertyName, propertyValue);
+
+        // Then
+        assertTrue(propertiesByServiceName.containsKey("service-name"));
+        Map<String, String> serviceProperties = propertiesByServiceName.get("service-name");
+        assertEquals("value", serviceProperties.get("load-balancer.property"));
+    }
+
+    @Test
+    void computeServiceProperty_FewValidProperties() {
+        // Given
+        Map<String, Map<String, String>> propertiesByServiceName = new HashMap<>();
+        String propertyName = "stork.service-name.load-balancer.property";
+        String propertyValue = "value";
+
+        // When
+        StorkConfigUtils.computeServiceProperty(propertiesByServiceName, propertyName, propertyValue);
+
+        String propertyName2 = "stork.service-name.load-balancer.property2";
+        String propertyValue2 = "value2";
+
+        StorkConfigUtils.computeServiceProperty(propertiesByServiceName, propertyName2, propertyValue2);
+
+        // Then
+        assertTrue(propertiesByServiceName.containsKey("service-name"));
+        Map<String, String> serviceProperties = propertiesByServiceName.get("service-name");
+        assertThat(serviceProperties).containsEntry("load-balancer.property", "value")
+                .containsEntry("load-balancer.property2", "value2");
+    }
+
+    @Test
+    void computeServiceProperty_InvalidProperty() {
+        // Given
+        Map<String, Map<String, String>> propertiesByServiceName = new HashMap<>();
+        String propertyName = "invalid.property";
+        String propertyValue = "value";
+
+        // When
+        StorkConfigUtils.computeServiceProperty(propertiesByServiceName, propertyName, propertyValue);
+
+        // Then
+        assertFalse(propertiesByServiceName.containsKey("service-name"));
+    }
+
+}
diff --git a/spring-boot/src/test/java/io/smallrye/stork/microprofile/SpringBootConfigProviderTest.java b/spring-boot/src/test/java/io/smallrye/stork/microprofile/SpringBootConfigProviderTest.java
new file mode 100644
index 00000000..706ea7c8
--- /dev/null
+++ b/spring-boot/src/test/java/io/smallrye/stork/microprofile/SpringBootConfigProviderTest.java
@@ -0,0 +1,256 @@
+package io.smallrye.stork.microprofile;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+import java.io.IOException;
+import java.time.Duration;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Properties;
+
+import org.junit.jupiter.api.AfterAll;
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.BeforeAll;
+import org.junit.jupiter.api.Test;
+import org.springframework.context.ApplicationContext;
+import org.springframework.context.annotation.AnnotationConfigApplicationContext;
+import org.springframework.core.env.ConfigurableEnvironment;
+import org.springframework.core.env.MutablePropertySources;
+import org.springframework.core.env.PropertiesPropertySource;
+
+import io.smallrye.stork.Stork;
+import io.smallrye.stork.api.LoadBalancer;
+import io.smallrye.stork.api.Metadata;
+import io.smallrye.stork.api.ServiceDiscovery;
+import io.smallrye.stork.api.ServiceRegistrar;
+import io.smallrye.stork.impl.RoundRobinLoadBalancer;
+import io.smallrye.stork.springboot.SpringBootApplicationContextProvider;
+import io.smallrye.stork.test.StorkTestUtils;
+import io.smallrye.stork.test.TestConfigProvider;
+import io.smallrye.stork.test.TestLoadBalancer1;
+import io.smallrye.stork.test.TestLoadBalancer2;
+import io.smallrye.stork.test.TestSd2Configuration;
+import io.smallrye.stork.test.TestServiceDiscovery;
+import io.smallrye.stork.test.TestServiceDiscovery2;
+import io.smallrye.stork.test.TestServiceRegistrarProvider;
+
+public class SpringBootConfigProviderTest {
+
+    public static final String MY_REGISTRAR = "my-registrar";
+    public static final String FIRST_SERVICE = "first-service";
+    public static final String SECOND_SERVICE = "second-service";
+    public static final String THIRD_SERVICE = "third-service";
+
+    private static int initialPriority;
+
+    @BeforeAll
+    public static void init() {
+        initialPriority = TestConfigProvider.getPriority();
+        TestConfigProvider.setPriority(10); // lower it for Stork to pick up the MP one
+    }
+
+    @AfterAll
+    public static void tearDown() {
+        TestConfigProvider.setPriority(initialPriority);
+    }
+
+    @AfterEach
+    void cleanUp() {
+        TestServiceRegistrarProvider.clear();
+    }
+
+    @Test
+    void shouldConfigureServiceRegistrar() throws IOException {
+        Map<String, String> properties = new HashMap<>();
+        properties.put("stork." + MY_REGISTRAR + ".service-discovery.type", "test-sd-1");
+        properties.put("stork." + MY_REGISTRAR + ".service-registrar.type", TestServiceRegistrarProvider.TYPE);
+        properties.put("stork." + MY_REGISTRAR + ".service-registrar.param1", "http://localhost:8080");
+        properties.put("stork." + MY_REGISTRAR + ".service-registrar.param2", "param2-value");
+
+        Stork stork = storkForConfig(properties);
+
+        ServiceRegistrar<TestServiceRegistrarProvider.TestMetadata> serviceRegistrar = stork.getService(MY_REGISTRAR)
+                .getServiceRegistrar();
+        assertThat(serviceRegistrar).isInstanceOf(TestServiceRegistrarProvider.TestServiceRegistrar.class);
+
+        serviceRegistrar.registerServiceInstance("foo", Metadata.of(TestServiceRegistrarProvider.TestMetadata.class)
+                .with(TestServiceRegistrarProvider.TestMetadata.FIRST, "1st"), "1.1.1.1", 8080)
+                .await().atMost(Duration.ofSeconds(5));
+
+        assertThat(TestServiceRegistrarProvider.getRegistrations()).hasSize(1);
+        TestServiceRegistrarProvider.Registration registration = TestServiceRegistrarProvider.getRegistrations().get(0);
+
+        assertThat(registration.serviceRegistrarName).isEqualTo(MY_REGISTRAR);
+        assertThat(registration.config.type()).isEqualTo(TestServiceRegistrarProvider.TYPE);
+        assertThat(registration.config.parameters()).containsEntry("param1", "http://localhost:8080")
+                .containsEntry("param2", "param2-value");
+        assertThat(registration.metadata.getMetadata().get(TestServiceRegistrarProvider.TestMetadata.FIRST)).isEqualTo("1st");
+        assertThat(registration.serviceRegistrarName).isEqualTo(MY_REGISTRAR);
+        assertThat(registration.serviceName).isEqualTo("foo");
+        assertThat(registration.ipAddress).isEqualTo("1.1.1.1");
+    }
+
+    @Test
+    void shouldConfigureServiceDiscoveryOnly() {
+        Map<String, String> properties = new HashMap<>();
+        properties.put("stork." + FIRST_SERVICE + ".service-discovery", "test-sd-1");
+        properties.put("stork." + FIRST_SERVICE + ".service-discovery.one", "http://localhost:8080");
+        properties.put("stork." + FIRST_SERVICE + ".service-discovery.two", "http://localhost:8081");
+
+        Stork stork = storkForConfig(properties);
+
+        // Use round-robin when not configured.
+        assertThat(stork.getService(FIRST_SERVICE).getLoadBalancer()).isInstanceOf(RoundRobinLoadBalancer.class);
+
+        ServiceDiscovery serviceDiscovery = stork.getService(FIRST_SERVICE).getServiceDiscovery();
+
+        assertThat(serviceDiscovery).isNotNull().isInstanceOf(TestServiceDiscovery.class);
+
+        TestServiceDiscovery sd = (TestServiceDiscovery) serviceDiscovery;
+        assertThat(sd.getType()).isEqualTo("test-sd-1");
+        assertThat(sd.getConfig().getOne()).isEqualTo("http://localhost:8080");
+        assertThat(sd.getConfig().getTwo()).isEqualTo("http://localhost:8081");
+    }
+
+    @Test
+    void shouldConfigureServiceDiscoveryOnlyUsingEmbeddedType() {
+        Map<String, String> properties = new HashMap<>();
+        properties.put("stork." + FIRST_SERVICE + ".service-discovery.type", "test-sd-1");
+        properties.put("stork." + FIRST_SERVICE + ".service-discovery.one", "http://localhost:8080");
+
+        Stork stork = storkForConfig(properties);
+
+        ServiceDiscovery serviceDiscovery = stork.getService(FIRST_SERVICE).getServiceDiscovery();
+
+        assertThat(serviceDiscovery).isNotNull().isInstanceOf(TestServiceDiscovery.class);
+
+        TestServiceDiscovery sd = (TestServiceDiscovery) serviceDiscovery;
+        assertThat(sd.getType()).isEqualTo("test-sd-1");
+        assertThat(sd.getConfig().getOne()).isEqualTo("http://localhost:8080");
+        assertThat(sd.getConfig().getTwo()).isEqualTo(null);
+    }
+
+    @Test
+    void shouldConfigureServiceDiscoveryAndLoadBalancer() {
+        Map<String, String> properties = new HashMap<>();
+        properties.put("stork." + SECOND_SERVICE + ".service-discovery", "test-sd-2");
+        properties.put("stork." + SECOND_SERVICE + ".load-balancer.type", "test-lb-2");
+        properties.put("stork." + SECOND_SERVICE + ".load-balancer.some-prop", "some-prop-value");
+        properties.put("stork." + SECOND_SERVICE + ".service-discovery.three", "http://localhost:8082");
+
+        Stork stork = storkForConfig(properties);
+
+        ServiceDiscovery serviceDiscovery = stork.getService(SECOND_SERVICE).getServiceDiscovery();
+        assertThat(serviceDiscovery).isNotNull().isInstanceOf(TestServiceDiscovery2.class);
+
+        TestServiceDiscovery2 sd = (TestServiceDiscovery2) serviceDiscovery;
+        assertThat(sd.getType()).isEqualTo("test-sd-2");
+
+        TestSd2Configuration sdConfig = sd.getConfig();
+        assertThat(sdConfig.getThree()).isEqualTo("http://localhost:8082");
+
+        LoadBalancer loadBalancer = stork.getService(SECOND_SERVICE).getLoadBalancer();
+        assertThat(loadBalancer).isInstanceOf(TestLoadBalancer2.class);
+
+        TestLoadBalancer2 lb = (TestLoadBalancer2) loadBalancer;
+
+        assertThat(lb.getServiceDiscovery()).isEqualTo(serviceDiscovery);
+        assertThat(lb.getType()).isEqualTo("test-lb-2");
+        var lbConfig = lb.getConfig();
+
+        assertThat(lbConfig.getSomeProp()).isEqualTo("some-prop-value");
+    }
+
+    @Test
+    void shouldHandleMultipleServices() {
+        Map<String, String> properties = new HashMap<>();
+        properties.put("stork." + SECOND_SERVICE + ".service-discovery", "test-sd-2");
+        properties.put("stork." + SECOND_SERVICE + ".load-balancer", "test-lb-2");
+        properties.put("stork." + SECOND_SERVICE + ".load-balancer.some-prop", "some-prop-value");
+        properties.put("stork." + SECOND_SERVICE + ".service-discovery.three", "http://localhost:8082");
+
+        properties.put("stork." + THIRD_SERVICE + ".service-discovery", "test-sd-1");
+        properties.put("stork." + THIRD_SERVICE + ".load-balancer", "test-lb-1");
+
+        Stork stork = storkForConfig(properties);
+
+        ServiceDiscovery serviceDiscovery = stork.getService(SECOND_SERVICE).getServiceDiscovery();
+        assertThat(serviceDiscovery).isNotNull().isInstanceOf(TestServiceDiscovery2.class);
+
+        TestServiceDiscovery2 sd = (TestServiceDiscovery2) serviceDiscovery;
+        assertThat(sd.getType()).isEqualTo("test-sd-2");
+
+        TestSd2Configuration sdConfig = sd.getConfig();
+        assertThat(sdConfig.getThree()).isEqualTo("http://localhost:8082");
+
+        LoadBalancer loadBalancer = stork.getService(SECOND_SERVICE).getLoadBalancer();
+        assertThat(loadBalancer).isInstanceOf(TestLoadBalancer2.class);
+
+        TestLoadBalancer2 lb = (TestLoadBalancer2) loadBalancer;
+
+        assertThat(lb.getServiceDiscovery()).isEqualTo(serviceDiscovery);
+        assertThat(lb.getType()).isEqualTo("test-lb-2");
+        var lbConfig = lb.getConfig();
+        assertThat(lbConfig.getSomeProp()).isEqualTo("some-prop-value");
+
+        serviceDiscovery = stork.getService(THIRD_SERVICE).getServiceDiscovery();
+        assertThat(serviceDiscovery).isInstanceOf(TestServiceDiscovery.class);
+        TestServiceDiscovery sd3 = (TestServiceDiscovery) serviceDiscovery;
+
+        assertThat(sd3.getType()).isEqualTo("test-sd-1");
+        assertThat(sd3.getConfig().getOne()).isNull();
+        assertThat(sd3.getConfig().getTwo()).isNull();
+
+        loadBalancer = stork.getService(THIRD_SERVICE).getLoadBalancer();
+        assertThat(loadBalancer).isInstanceOf(TestLoadBalancer1.class);
+        TestLoadBalancer1 lb1 = (TestLoadBalancer1) loadBalancer;
+
+        assertThat(lb1.getType()).isEqualTo("test-lb-1");
+    }
+
+    @Test
+    void shouldHandleServiceNamesInQuotes() {
+        String serviceName = "my.service";
+
+        Map<String, String> properties = new HashMap<>();
+        properties.put("stork.\"my.service\".service-discovery", "test-sd-2");
+        properties.put("stork.\"my.service\".load-balancer", "test-lb-2");
+        properties.put("stork.\"my.service\".service-discovery.three", "http://localhost:8082");
+
+        Stork stork = storkForConfig(properties);
+
+        ServiceDiscovery serviceDiscovery = stork.getService(serviceName).getServiceDiscovery();
+        assertThat(serviceDiscovery).isNotNull().isInstanceOf(TestServiceDiscovery2.class);
+
+        TestServiceDiscovery2 sd = (TestServiceDiscovery2) serviceDiscovery;
+        assertThat(sd.getType()).isEqualTo("test-sd-2");
+
+        TestSd2Configuration sdConfig = sd.getConfig();
+        assertThat(sdConfig.getThree()).isEqualTo("http://localhost:8082");
+
+        LoadBalancer loadBalancer = stork.getService(serviceName).getLoadBalancer();
+        assertThat(loadBalancer).isInstanceOf(TestLoadBalancer2.class);
+
+        TestLoadBalancer2 lb = (TestLoadBalancer2) loadBalancer;
+
+        assertThat(lb.getServiceDiscovery()).isEqualTo(serviceDiscovery);
+        assertThat(lb.getType()).isEqualTo("test-lb-2");
+    }
+
+    private Stork storkForConfig(Map<String, String> props) {
+
+        Properties properties = new Properties();
+        properties.putAll(props);
+
+        ApplicationContext context = new AnnotationConfigApplicationContext();
+        ConfigurableEnvironment environment = (ConfigurableEnvironment) context.getEnvironment();
+        MutablePropertySources propertySources = environment.getPropertySources();
+        propertySources.addFirst(new PropertiesPropertySource("applicationProperties", properties));
+
+        SpringBootApplicationContextProvider springBootApplicationContextProvider = new SpringBootApplicationContextProvider();
+        springBootApplicationContextProvider.setApplicationContext(context);
+
+        return StorkTestUtils.getNewStorkInstance();
+    }
+
+}