diff --git a/README.md b/README.md
index fa7f74808..9c7576b87 100644
--- a/README.md
+++ b/README.md
@@ -572,6 +572,14 @@ Covers two areas related to Spring Web:
- Custom error handlers.
- Cooperation with Qute templating engine.
+### `spring/spring-properties`
+Exploratory testing for the `quarkus-spring-boot-properties` Quarkus extension. The application consists of a REST endpoint
+with some different approaches to inject properties.
+
+Current limitations:
+- Relaxing name convention is not supported and it won't be supported: https://github.com/quarkusio/quarkus/issues/12483
+- The annotation `@ConstructorBinding` is not supported yet: https://github.com/quarkusio/quarkus/issues/19364
+
### `infinispan-client`
Verifies the way of the sharing cache by Datagrid operator and Infinispan cluster and data consistency after failures.
diff --git a/pom.xml b/pom.xml
index dd2038764..848d34bbb 100644
--- a/pom.xml
+++ b/pom.xml
@@ -91,6 +91,7 @@
kamelet
spring/spring-data
spring/spring-web
+ spring/spring-properties
logging/jboss
cache/caffeine
diff --git a/spring/spring-properties/pom.xml b/spring/spring-properties/pom.xml
new file mode 100644
index 000000000..010d46d71
--- /dev/null
+++ b/spring/spring-properties/pom.xml
@@ -0,0 +1,23 @@
+
+
+ 4.0.0
+
+ io.quarkus.ts.qe
+ parent
+ 1.0.0-SNAPSHOT
+ ../..
+
+ spring-properties
+ jar
+ Quarkus QE TS: Spring: Properties
+
+
+ io.quarkus
+ quarkus-spring-boot-properties
+
+
+ io.quarkus
+ quarkus-spring-web
+
+
+
diff --git a/spring/spring-properties/src/main/java/io/quarkus/ts/spring/properties/CollectionsController.java b/spring/spring-properties/src/main/java/io/quarkus/ts/spring/properties/CollectionsController.java
new file mode 100644
index 000000000..5bdc7ecf4
--- /dev/null
+++ b/spring/spring-properties/src/main/java/io/quarkus/ts/spring/properties/CollectionsController.java
@@ -0,0 +1,49 @@
+package io.quarkus.ts.spring.properties;
+
+import java.util.stream.Collectors;
+
+import javax.inject.Inject;
+
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+@RestController
+@RequestMapping("/collections")
+public class CollectionsController {
+
+ // Using `@Inject` instead of `@Autowired` to verify we can use both.
+ @Inject
+ ListWiringProperties listProperties;
+
+ // Injecting maps is not supported. Reported in https://github.com/quarkusio/quarkus/issues/19366
+ // @Autowired
+ // MapWiringProperties mapProperties;
+
+ @GetMapping("/list/strings")
+ public String listOfStrings() {
+ return listProperties.strings.stream().collect(Collectors.joining(", "));
+ }
+
+ // Injecting lists with objects is not unsupported. Reported in https://github.com/quarkusio/quarkus/issues/19365
+ // @GetMapping("/list/persons")
+ // public String listOfPersons() {
+ // return listProperties.persons.stream().map(Object::toString).collect(Collectors.joining(", "));
+ // }
+ //
+
+ // Injecting maps is not supported. Reported in https://github.com/quarkusio/quarkus/issues/19366
+ // @GetMapping("/map/integers")
+ // public String mapOfIntegers() {
+ // return mapProperties.integers.entrySet().stream()
+ // .map(e -> e.getKey() + "=" + e.getValue())
+ // .collect(Collectors.joining(", "));
+ // }
+ //
+ // @GetMapping("/map/persons")
+ // public String mapOfPersons() {
+ // return mapProperties.persons.entrySet().stream()
+ // .map(e -> e.getKey() + "=" + e.getValue())
+ // .collect(Collectors.joining(", "));
+ // }
+}
diff --git a/spring/spring-properties/src/main/java/io/quarkus/ts/spring/properties/GreetingController.java b/spring/spring-properties/src/main/java/io/quarkus/ts/spring/properties/GreetingController.java
new file mode 100644
index 000000000..79fc19db1
--- /dev/null
+++ b/spring/spring-properties/src/main/java/io/quarkus/ts/spring/properties/GreetingController.java
@@ -0,0 +1,39 @@
+package io.quarkus.ts.spring.properties;
+
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+@RestController
+@RequestMapping("/greeting")
+public class GreetingController {
+
+ @Autowired
+ GreetingProperties properties;
+
+ @GetMapping("/text")
+ public String text() {
+ return properties.text;
+ }
+
+ @GetMapping("/textWithDefault")
+ public String textWithDefault() {
+ return properties.textWithDefault;
+ }
+
+ @GetMapping("/textPrivate")
+ public String textPrivate() {
+ return properties.getTextPrivate();
+ }
+
+ @GetMapping("/textOptional")
+ public String textOptional() {
+ return properties.textOptional.orElse("empty!");
+ }
+
+ @GetMapping("/message")
+ public String message() {
+ return properties.message.toString();
+ }
+}
diff --git a/spring/spring-properties/src/main/java/io/quarkus/ts/spring/properties/GreetingProperties.java b/spring/spring-properties/src/main/java/io/quarkus/ts/spring/properties/GreetingProperties.java
new file mode 100644
index 000000000..da68f7f40
--- /dev/null
+++ b/spring/spring-properties/src/main/java/io/quarkus/ts/spring/properties/GreetingProperties.java
@@ -0,0 +1,42 @@
+package io.quarkus.ts.spring.properties;
+
+import java.util.Optional;
+
+import org.springframework.boot.context.properties.ConfigurationProperties;
+
+@ConfigurationProperties("greeting")
+public class GreetingProperties {
+
+ // Cover private field using public getter/setter
+ private String textPrivate;
+
+ // Cover optional fields
+ public Optional textOptional;
+
+ // Cover direct field binding
+ public String text;
+
+ // Cover field with defaults
+ public String textWithDefault = "Hola";
+
+ // Cover group fields
+ public NestedMessage message;
+
+ public String getTextPrivate() {
+ return textPrivate;
+ }
+
+ public void setTextPrivate(String textPrivate) {
+ this.textPrivate = textPrivate;
+ }
+
+ public static class NestedMessage {
+ public String text;
+ public String person = "unknown";
+
+ @Override
+ public String toString() {
+ return text + " " + person + "!";
+ }
+ }
+}
\ No newline at end of file
diff --git a/spring/spring-properties/src/main/java/io/quarkus/ts/spring/properties/ListWiringProperties.java b/spring/spring-properties/src/main/java/io/quarkus/ts/spring/properties/ListWiringProperties.java
new file mode 100644
index 000000000..3c0bc7077
--- /dev/null
+++ b/spring/spring-properties/src/main/java/io/quarkus/ts/spring/properties/ListWiringProperties.java
@@ -0,0 +1,26 @@
+package io.quarkus.ts.spring.properties;
+
+import java.util.List;
+
+import org.springframework.boot.context.properties.ConfigurationProperties;
+
+@ConfigurationProperties("lists")
+public class ListWiringProperties {
+
+ // Cover injection of lists of strings;
+ public List strings;
+
+ // Inject of complex objects in lists is not supported: https://github.com/quarkusio/quarkus/issues/19365
+ // // Cover injection of classes
+ // public List persons;
+ //
+ // public static class Person {
+ // public String name;
+ // public int age;
+ //
+ // @Override
+ // public String toString() {
+ // return String.format("person[%s:%s]", name, age);
+ // }
+ // }
+}
\ No newline at end of file
diff --git a/spring/spring-properties/src/main/java/io/quarkus/ts/spring/properties/MapWiringProperties.java b/spring/spring-properties/src/main/java/io/quarkus/ts/spring/properties/MapWiringProperties.java
new file mode 100644
index 000000000..70c42221c
--- /dev/null
+++ b/spring/spring-properties/src/main/java/io/quarkus/ts/spring/properties/MapWiringProperties.java
@@ -0,0 +1,24 @@
+package io.quarkus.ts.spring.properties;
+
+import java.util.Map;
+
+// Injecting maps is not supported. Reported in https://github.com/quarkusio/quarkus/issues/19366
+// @ConfigurationProperties("maps")
+public class MapWiringProperties {
+
+ // Cover injection of maps of integers and strings;
+ public Map integers;
+
+ // Cover injection of maps of string and classes
+ public Map persons;
+
+ public static class Person {
+ public String name;
+ public int age;
+
+ @Override
+ public String toString() {
+ return String.format("person[%s:%s]", name, age);
+ }
+ }
+}
\ No newline at end of file
diff --git a/spring/spring-properties/src/main/java/io/quarkus/ts/spring/properties/ValueController.java b/spring/spring-properties/src/main/java/io/quarkus/ts/spring/properties/ValueController.java
new file mode 100644
index 000000000..36cd917c6
--- /dev/null
+++ b/spring/spring-properties/src/main/java/io/quarkus/ts/spring/properties/ValueController.java
@@ -0,0 +1,52 @@
+package io.quarkus.ts.spring.properties;
+
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+@RestController
+@RequestMapping("/values")
+public class ValueController {
+
+ @Value("${values.text}")
+ String fieldUsingValue;
+
+ @Value("${values.list}")
+ String[] fieldUsingArray;
+
+ // Complex SpEL expresions is not supported: https://github.com/quarkusio/quarkus/issues/19368"
+ // @Value("#{'${values.list}'.split(',')}")
+ // List fieldUsingList;
+
+ // Complex SpEL expresions is not supported: https://github.com/quarkusio/quarkus/issues/19368"
+ // @Value("#{${values.map}}")
+ // Map fieldUsingMap;
+
+ @GetMapping("/fieldUsingValue")
+ public String fieldUsingValue() {
+ return fieldUsingValue;
+ }
+
+ @GetMapping("/fieldUsingArray")
+ public String fieldUsingArray() {
+ return Stream.of(fieldUsingArray).collect(Collectors.joining(", "));
+ }
+
+ // Complex SpEL expresions is not supported: https://github.com/quarkusio/quarkus/issues/19368"
+ // @GetMapping("/fieldUsingList")
+ // public String fieldUsingList() {
+ // return fieldUsingList.stream().collect(Collectors.joining(", "));
+ // }
+ //
+ // @GetMapping("/fieldUsingMap")
+ // public String fieldUsingMap() {
+ // return fieldUsingMap.entrySet().stream()
+ // .map(e -> e.getKey() + ": " + e.getValue())
+ // .collect(Collectors.joining(", "));
+ // }
+
+}
diff --git a/spring/spring-properties/src/main/resources/application.properties b/spring/spring-properties/src/main/resources/application.properties
new file mode 100644
index 000000000..673914499
--- /dev/null
+++ b/spring/spring-properties/src/main/resources/application.properties
@@ -0,0 +1,26 @@
+# Basic Fields
+greeting.text=hello
+greeting.text-private=private hello!
+greeting.message.text=Hola
+
+# List Fields
+lists.strings[0]=Value 1
+
+lists.persons[0].name=Sarah
+lists.persons[0].age=19
+lists.persons[1].name=Terminator
+lists.persons[1].age=999
+
+# Map Fields
+maps.integers.1=Value 1
+maps.integers.2=Value 2
+
+maps.persons.character1.name=Sarah
+maps.persons.character1.age=19
+maps.persons.character2.name=Terminator
+maps.persons.character2.age=999
+
+# Values
+values.text=hello
+values.list=A,B
+values.map={key1: '1', key2: '2', key3: '3'}
\ No newline at end of file
diff --git a/spring/spring-properties/src/test/java/io/quarkus/ts/spring/properties/SpringPropertiesIT.java b/spring/spring-properties/src/test/java/io/quarkus/ts/spring/properties/SpringPropertiesIT.java
new file mode 100644
index 000000000..7e95444a4
--- /dev/null
+++ b/spring/spring-properties/src/test/java/io/quarkus/ts/spring/properties/SpringPropertiesIT.java
@@ -0,0 +1,136 @@
+package io.quarkus.ts.spring.properties;
+
+import static io.restassured.RestAssured.given;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+import org.apache.http.HttpStatus;
+import org.junit.jupiter.api.Disabled;
+import org.junit.jupiter.api.Test;
+
+import io.quarkus.test.bootstrap.RestService;
+import io.quarkus.test.scenarios.QuarkusScenario;
+import io.quarkus.test.services.QuarkusApplication;
+
+@QuarkusScenario
+public class SpringPropertiesIT {
+
+ @QuarkusApplication
+ static RestService app = new RestService();
+
+ @Test
+ public void shouldInjectPublicFieldFromConfigurationPropertiesIntoController() {
+ // "hello" is defined in application.properties
+ assertEquals("hello", getGreetingsWithPath("/text"));
+ }
+
+ @Test
+ public void shouldInjectDefaultPublicFieldFromConfigurationPropertiesIntoController() {
+ // "hola" is the default value in GreetingProperties.java as there is no property defined yet
+ assertEquals("Hola", getGreetingsWithPath("/textWithDefault"));
+
+ setApplicationProperty("greeting.text-with-default", "Alo!");
+ assertEquals("Alo!", getGreetingsWithPath("/textWithDefault"));
+ }
+
+ @Test
+ public void shouldInjectPrivateFieldFromConfigurationPropertiesIntoController() {
+ // "private hello!" is defined in application.properties
+ assertEquals("private hello!", getGreetingsWithPath("/textPrivate"));
+ }
+
+ @Test
+ public void shouldInjectOptionalPublicFieldFromConfigurationPropertiesIntoController() {
+ // "empty" is returned when the property is not in application.properties
+ assertEquals("empty!", getGreetingsWithPath("/textOptional"));
+
+ setApplicationProperty("greeting.text-optional", "Hi!");
+ assertEquals("Hi!", getGreetingsWithPath("/textOptional"));
+ }
+
+ @Test
+ public void shouldInjectGroupPublicFieldFromConfigurationPropertiesIntoController() {
+ // "Hola unknown!" is returned when the property person is not in application.properties
+ assertEquals("Hola unknown!", getGreetingsWithPath("/message"));
+
+ setApplicationProperty("greeting.message.person", "Sarah");
+ assertEquals("Hola Sarah!", getGreetingsWithPath("/message"));
+ }
+
+ @Test
+ public void shouldInjectListsOfStringFromConfigurationProperties() {
+ // These values comes from the application.properties
+ assertEquals("Value 1", getCollectionsWithPath("/list/strings"));
+
+ setApplicationProperty("lists.strings[1]", "Value 2");
+ assertEquals("Value 1, Value 2", getCollectionsWithPath("/list/strings"));
+ }
+
+ @Disabled("Inject of complex objects in lists is not supported: https://github.com/quarkusio/quarkus/issues/19365")
+ @Test
+ public void shouldInjectListsOfPersonFromConfigurationProperties() {
+ // These values comes from the application.properties
+ assertEquals("person[Sarah:19], person[Terminator:999]", getCollectionsWithPath("/list/persons"));
+ }
+
+ @Disabled("Inject maps is not supported: https://github.com/quarkusio/quarkus/issues/19366")
+ @Test
+ public void shouldInjectMapsOfIntegerFromConfigurationProperties() {
+ // These values comes from the application.properties
+ assertEquals("1=Value 1, 2=Value 2", getCollectionsWithPath("/maps/integers"));
+ }
+
+ @Disabled("Inject maps is not supported: https://github.com/quarkusio/quarkus/issues/19366")
+ @Test
+ public void shouldInjectMapsOfPersonFromConfigurationProperties() {
+ // These values comes from the application.properties
+ assertEquals("character1=person[Sarah:19], character2=person[Terminator:999]",
+ getCollectionsWithPath("/maps/persons"));
+ }
+
+ @Test
+ public void shouldInjectFieldsUsingValueAnnotation() {
+ assertEquals("hello", getValuesWithPath("/fieldUsingValue"));
+ }
+
+ @Test
+ public void shouldInjectArrayFieldsUsingValueAnnotation() {
+ assertEquals("A, B", getValuesWithPath("/fieldUsingArray"));
+ }
+
+ @Disabled("Complex SpEL expresions is not supported: https://github.com/quarkusio/quarkus/issues/19368")
+ @Test
+ public void shouldInjectListFieldsUsingValueAnnotation() {
+ assertEquals("A, B", getValuesWithPath("/fieldUsingList"));
+ }
+
+ @Disabled("Complex SpEL expresions is not supported: https://github.com/quarkusio/quarkus/issues/19368")
+ @Test
+ public void shouldInjectMapFieldsUsingValueAnnotation() {
+ assertEquals("key1: 1, key2: 2, key3: 3", getValuesWithPath("/fieldUsingMap"));
+ }
+
+ private void setApplicationProperty(String name, String value) {
+ app.stop();
+ app.withProperty(name, value);
+ app.start();
+ }
+
+ private String getValuesWithPath(String path) {
+ return get("/values" + path);
+ }
+
+ private String getCollectionsWithPath(String path) {
+ return get("/collections" + path);
+ }
+
+ private String getGreetingsWithPath(String path) {
+ return get("/greeting" + path);
+ }
+
+ private String get(String path) {
+ return given().when().get(path)
+ .then()
+ .statusCode(HttpStatus.SC_OK)
+ .extract().asString();
+ }
+}