diff --git a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/properties/source/ConfigurationPropertyName.java b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/properties/source/ConfigurationPropertyName.java
index b0334a1059..d6a833d8a6 100644
--- a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/properties/source/ConfigurationPropertyName.java
+++ b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/properties/source/ConfigurationPropertyName.java
@@ -29,7 +29,7 @@ import org.springframework.util.ObjectUtils;
/**
* A configuration property name composed of elements separated by dots. User created
* names may contain the characters "{@code a-z}" "{@code 0-9}") and "{@code -}", they
- * must be lower-case and must start with a letter. The "{@code -}" is used purely for
+ * must be lower-case and must start with an alpha-numeric character. The "{@code -}" is used purely for
* formatting, i.e. "{@code foo-bar}" and "{@code foobar}" are considered equivalent.
*
* The "{@code [}" and "{@code ]}" characters may be used to indicate an associative
@@ -675,7 +675,7 @@ public final class ConfigurationPropertyName
}
public static boolean isValidChar(char ch, int index) {
- return isAlpha(ch) || (index != 0 && (isNumeric(ch) || ch == '-'));
+ return isAlpha(ch) || isNumeric(ch) || (index != 0 && ch == '-');
}
private static boolean isAlpha(char ch) {
diff --git a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/context/properties/ConfigurationPropertiesBindingPostProcessorTests.java b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/context/properties/ConfigurationPropertiesBindingPostProcessorTests.java
index a2293d5749..6f0ba6e31d 100644
--- a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/context/properties/ConfigurationPropertiesBindingPostProcessorTests.java
+++ b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/context/properties/ConfigurationPropertiesBindingPostProcessorTests.java
@@ -273,6 +273,20 @@ public class ConfigurationPropertiesBindingPostProcessorTests {
this.context.refresh();
}
+ @Test
+ public void bindToMapWithNumericKey() {
+ this.context = new AnnotationConfigApplicationContext();
+ MutablePropertySources sources = this.context.getEnvironment()
+ .getPropertySources();
+ Map source = new LinkedHashMap<>();
+ source.put("sample.foos.1.name", "One");
+ sources.addFirst(new MapPropertySource("test-source", source));
+ this.context.register(NumericKeyConfiguration.class);
+ this.context.refresh();
+ NumericKeyConfiguration foo = this.context.getBean(NumericKeyConfiguration.class);
+ assertThat(foo.getFoos().get("1")).isNotNull();
+ }
+
private void prepareConverterContext(Class>... config) {
this.context = new AnnotationConfigApplicationContext();
MutablePropertySources sources = this.context.getEnvironment()
@@ -604,4 +618,29 @@ public class ConfigurationPropertiesBindingPostProcessorTests {
}
+ @Configuration
+ @EnableConfigurationProperties
+ @ConfigurationProperties(prefix = "sample")
+ static class NumericKeyConfiguration {
+
+ private Map foos = new LinkedHashMap<>();
+
+ public Map getFoos() {
+ return this.foos;
+ }
+
+ static class Foo {
+
+ private String name;
+
+ public String getName() {
+ return this.name;
+ }
+
+ public void setName(String name) {
+ this.name = name;
+ }
+ }
+ }
+
}
diff --git a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/context/properties/source/ConfigurationPropertyNameTests.java b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/context/properties/source/ConfigurationPropertyNameTests.java
index aa89e8ab53..e6ea34d050 100644
--- a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/context/properties/source/ConfigurationPropertyNameTests.java
+++ b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/context/properties/source/ConfigurationPropertyNameTests.java
@@ -49,13 +49,6 @@ public class ConfigurationPropertyNameTests {
ConfigurationPropertyName.of(null);
}
- @Test
- public void ofNameShouldNotStartWithNumber() {
- this.thrown.expect(InvalidConfigurationPropertyNameException.class);
- this.thrown.expectMessage("is not valid");
- ConfigurationPropertyName.of("1foo");
- }
-
@Test
public void ofNameShouldNotStartWithDash() {
this.thrown.expect(InvalidConfigurationPropertyNameException.class);
@@ -107,6 +100,15 @@ public class ConfigurationPropertyNameTests {
assertThat(name.isIndexed(0)).isFalse();
}
+ @Test
+ public void ofNameWhenStartsWithNumber() {
+ ConfigurationPropertyName name = ConfigurationPropertyName.of("1foo");
+ assertThat(name.toString()).isEqualTo("1foo");
+ assertThat(name.getNumberOfElements()).isEqualTo(1);
+ assertThat(name.getElement(0, Form.ORIGINAL)).isEqualTo("1foo");
+ assertThat(name.isIndexed(0)).isFalse();
+ }
+
@Test
public void ofNameWhenRunOnAssociative() {
ConfigurationPropertyName name = ConfigurationPropertyName.of("foo[bar]");
@@ -420,8 +422,8 @@ public class ConfigurationPropertyNameTests {
@Test
public void appendWhenElementNameIsNotValidShouldThrowException() {
this.thrown.expect(InvalidConfigurationPropertyNameException.class);
- this.thrown.expectMessage("Configuration property name '1bar' is not valid");
- ConfigurationPropertyName.of("foo").append("1bar");
+ this.thrown.expectMessage("Configuration property name '-bar' is not valid");
+ ConfigurationPropertyName.of("foo").append("-bar");
}
@Test
@@ -589,7 +591,7 @@ public class ConfigurationPropertyNameTests {
@Test
public void isValidWhenNotValidShouldReturnFalse() {
assertThat(ConfigurationPropertyName.isValid(null)).isFalse();
- assertThat(ConfigurationPropertyName.isValid("1foo")).isFalse();
+ assertThat(ConfigurationPropertyName.isValid("-foo")).isFalse();
assertThat(ConfigurationPropertyName.isValid("FooBar")).isFalse();
assertThat(ConfigurationPropertyName.isValid("foo!bar")).isFalse();
}