diff --git a/spring-boot-docs/src/main/asciidoc/spring-boot-features.adoc b/spring-boot-docs/src/main/asciidoc/spring-boot-features.adoc
index a8a9e46268..87c76bee83 100644
--- a/spring-boot-docs/src/main/asciidoc/spring-boot-features.adoc
+++ b/spring-boot-docs/src/main/asciidoc/spring-boot-features.adoc
@@ -782,7 +782,9 @@ annotations to your `@ConfigurationProperties` class:
----
You can also add a custom Spring `Validator` by creating a bean definition called
-`configurationPropertiesValidator`.
+`configurationPropertiesValidator`. There is a
+{github-code}/spring-boot-samples/spring-boot-sample-property-validation[Validation sample]
+so you can see how to set things up.
TIP: The `spring-boot-actuator` module includes an endpoint that exposes all
`@ConfigurationProperties` beans. Simply point your web browser to `/configprops`
diff --git a/spring-boot-samples/README.adoc b/spring-boot-samples/README.adoc
index 1186970424..549e308ae1 100644
--- a/spring-boot-samples/README.adoc
+++ b/spring-boot-samples/README.adoc
@@ -70,6 +70,8 @@
-- A spring integration application
* link:spring-boot-sample-profile[spring-boot-sample-profile]
-- example showing Spring's `@profile` support
+* link:spring-boot-sample-property-validation[spring-boot-sample-property-validation]
+ -- example showing the usage of @ConfigurationProperties` with a Spring's `Validator`
* link:spring-boot-sample-parent-context[spring-boot-sample-parent-context]
-- example showing an `ApplicationContext` with a parent
* link:spring-boot-sample-aop[spring-boot-sample-aop]
diff --git a/spring-boot-samples/pom.xml b/spring-boot-samples/pom.xml
index 00ed00afbd..db59230868 100644
--- a/spring-boot-samples/pom.xml
+++ b/spring-boot-samples/pom.xml
@@ -67,6 +67,7 @@
spring-boot-sample-metrics-redis
spring-boot-sample-parent-context
spring-boot-sample-profile
+ spring-boot-sample-property-validation
spring-boot-sample-secure
spring-boot-sample-secure-oauth2
spring-boot-sample-servlet
diff --git a/spring-boot-samples/spring-boot-sample-property-validation/application.properties b/spring-boot-samples/spring-boot-sample-property-validation/application.properties
new file mode 100644
index 0000000000..2a20a86341
--- /dev/null
+++ b/spring-boot-samples/spring-boot-sample-property-validation/application.properties
@@ -0,0 +1,2 @@
+host: 192.168.0.1
+port: 8080
\ No newline at end of file
diff --git a/spring-boot-samples/spring-boot-sample-property-validation/build.gradle b/spring-boot-samples/spring-boot-sample-property-validation/build.gradle
new file mode 100644
index 0000000000..1d71e48466
--- /dev/null
+++ b/spring-boot-samples/spring-boot-sample-property-validation/build.gradle
@@ -0,0 +1,49 @@
+buildscript {
+ ext {
+ springBootVersion = '1.3.0.BUILD-SNAPSHOT'
+ }
+ repositories {
+ // NOTE: You should declare only repositories that you need here
+ mavenLocal()
+ mavenCentral()
+ maven { url "http://repo.spring.io/release" }
+ maven { url "http://repo.spring.io/milestone" }
+ maven { url "http://repo.spring.io/snapshot" }
+ }
+ dependencies {
+ classpath("org.springframework.boot:spring-boot-gradle-plugin:${springBootVersion}")
+ }
+}
+
+apply plugin: 'java'
+apply plugin: 'eclipse'
+apply plugin: 'idea'
+apply plugin: 'spring-boot'
+
+jar {
+ baseName = 'spring-boot-sample-property-validation'
+ version = '0.0.0'
+}
+
+run {
+ systemProperties = System.properties
+}
+
+repositories {
+ // NOTE: You should declare only repositories that you need here
+ mavenLocal()
+ mavenCentral()
+ maven { url "http://repo.spring.io/release" }
+ maven { url "http://repo.spring.io/milestone" }
+ maven { url "http://repo.spring.io/snapshot" }
+}
+
+dependencies {
+ compile("org.springframework.boot:spring-boot-starter")
+ compile("org.hibernate:hibernate-validator")
+ testCompile("org.springframework.boot:spring-boot-starter-test")
+}
+
+task wrapper(type: Wrapper) {
+ gradleVersion = '1.6'
+}
diff --git a/spring-boot-samples/spring-boot-sample-property-validation/pom.xml b/spring-boot-samples/spring-boot-sample-property-validation/pom.xml
new file mode 100644
index 0000000000..d94035fe02
--- /dev/null
+++ b/spring-boot-samples/spring-boot-sample-property-validation/pom.xml
@@ -0,0 +1,45 @@
+
+
+ 4.0.0
+
+
+ org.springframework.boot
+ spring-boot-samples
+ 1.3.0.BUILD-SNAPSHOT
+
+ spring-boot-sample-property-validation
+ Spring Boot Property Validation Sample
+ Spring Boot Property Validation Sample
+ http://projects.spring.io/spring-boot/
+
+ Pivotal Software, Inc.
+ http://www.spring.io
+
+
+ ${basedir}/../..
+
+
+
+ org.springframework.boot
+ spring-boot-starter
+
+
+ org.hibernate
+ hibernate-validator
+ 5.1.3.Final
+
+
+ org.springframework.boot
+ spring-boot-starter-test
+ test
+
+
+
+
+
+ org.springframework.boot
+ spring-boot-maven-plugin
+
+
+
+
diff --git a/spring-boot-samples/spring-boot-sample-property-validation/src/main/java/sample/propertyvalidation/ConfigurationPropertiesValidator.java b/spring-boot-samples/spring-boot-sample-property-validation/src/main/java/sample/propertyvalidation/ConfigurationPropertiesValidator.java
new file mode 100644
index 0000000000..1cd3cf8bc4
--- /dev/null
+++ b/spring-boot-samples/spring-boot-sample-property-validation/src/main/java/sample/propertyvalidation/ConfigurationPropertiesValidator.java
@@ -0,0 +1,63 @@
+/*
+ * Copyright 2012-2015 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package sample.propertyvalidation;
+
+import org.springframework.boot.context.properties.ConfigurationProperties;
+import org.springframework.core.annotation.AnnotationUtils;
+import org.springframework.stereotype.Component;
+import org.springframework.validation.Errors;
+import org.springframework.validation.ValidationUtils;
+import org.springframework.validation.Validator;
+
+import java.util.HashSet;
+import java.util.Set;
+import java.util.regex.Pattern;
+
+@Component(value = "configurationPropertiesValidator")
+public class ConfigurationPropertiesValidator implements Validator {
+
+ public static final String IP_REGEX = "^(?:[0-9]{1,3}\\.){3}[0-9]{1,3}$";
+
+ final Pattern pattern = Pattern.compile(IP_REGEX);
+
+ private Set validatedClasses = new HashSet() {{
+ add(SampleProperties.class.getName());
+ }};
+
+ @Override
+ public boolean supports(Class> aClass) {
+ return AnnotationUtils.findAnnotation(aClass, ConfigurationProperties.class) != null;
+ }
+
+ @Override
+ public void validate(Object o, Errors errors) {
+ if(validatedClasses.contains(o.getClass().getName())) {
+ doValidation(o, errors);
+ }
+ }
+
+ private void doValidation(Object o, Errors errors) {
+ ValidationUtils.rejectIfEmpty(errors, "host", "host.empty");
+ ValidationUtils.rejectIfEmpty(errors, "port", "port.empty");
+
+ SampleProperties properties = (SampleProperties) o;
+ if(!pattern.matcher(properties.getHost()).matches()) {
+ errors.rejectValue("host", "Invalid host");
+ }
+ }
+
+}
diff --git a/spring-boot-samples/spring-boot-sample-property-validation/src/main/java/sample/propertyvalidation/SampleProperties.java b/spring-boot-samples/spring-boot-sample-property-validation/src/main/java/sample/propertyvalidation/SampleProperties.java
new file mode 100644
index 0000000000..ce8be115d8
--- /dev/null
+++ b/spring-boot-samples/spring-boot-sample-property-validation/src/main/java/sample/propertyvalidation/SampleProperties.java
@@ -0,0 +1,45 @@
+/*
+ * Copyright 2012-2015 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package sample.propertyvalidation;
+
+import org.springframework.boot.context.properties.ConfigurationProperties;
+import org.springframework.stereotype.Component;
+
+@Component
+@ConfigurationProperties
+public class SampleProperties {
+
+ private String host;
+
+ private Integer port;
+
+ public String getHost() {
+ return host;
+ }
+
+ public void setHost(String host) {
+ this.host = host;
+ }
+
+ public Integer getPort() {
+ return port;
+ }
+
+ public void setPort(Integer port) {
+ this.port = port;
+ }
+}
diff --git a/spring-boot-samples/spring-boot-sample-property-validation/src/main/java/sample/propertyvalidation/SamplePropertyValidationApplication.java b/spring-boot-samples/spring-boot-sample-property-validation/src/main/java/sample/propertyvalidation/SamplePropertyValidationApplication.java
new file mode 100644
index 0000000000..023f32ae48
--- /dev/null
+++ b/spring-boot-samples/spring-boot-sample-property-validation/src/main/java/sample/propertyvalidation/SamplePropertyValidationApplication.java
@@ -0,0 +1,43 @@
+/*
+ * Copyright 2012-2015 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package sample.propertyvalidation;
+
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.CommandLineRunner;
+import org.springframework.boot.SpringApplication;
+import org.springframework.boot.autoconfigure.SpringBootApplication;
+
+import org.springframework.boot.context.properties.EnableConfigurationProperties;
+
+@SpringBootApplication
+@EnableConfigurationProperties
+public class SamplePropertyValidationApplication implements CommandLineRunner {
+
+ @Autowired
+ private SampleProperties properties;
+
+ @Override
+ public void run(String... args) {
+ System.out.println("host: " + this.properties.getHost());
+ System.out.println("port:" + this.properties.getPort());
+ }
+
+ public static void main(String[] args) throws Exception {
+ SpringApplication.run(SamplePropertyValidationApplication.class, args);
+ }
+
+}
diff --git a/spring-boot-samples/spring-boot-sample-property-validation/src/main/resources/application.properties b/spring-boot-samples/spring-boot-sample-property-validation/src/main/resources/application.properties
new file mode 100644
index 0000000000..2a20a86341
--- /dev/null
+++ b/spring-boot-samples/spring-boot-sample-property-validation/src/main/resources/application.properties
@@ -0,0 +1,2 @@
+host: 192.168.0.1
+port: 8080
\ No newline at end of file
diff --git a/spring-boot-samples/spring-boot-sample-property-validation/src/test/java/sample/propertyvalidation/SamplePropertyValidationApplicationTests.java b/spring-boot-samples/spring-boot-sample-property-validation/src/test/java/sample/propertyvalidation/SamplePropertyValidationApplicationTests.java
new file mode 100644
index 0000000000..e8c61d915b
--- /dev/null
+++ b/spring-boot-samples/spring-boot-sample-property-validation/src/test/java/sample/propertyvalidation/SamplePropertyValidationApplicationTests.java
@@ -0,0 +1,85 @@
+/*
+ * Copyright 2012-2015 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package sample.propertyvalidation;
+
+import org.junit.Test;
+import org.springframework.beans.factory.BeanCreationException;
+import org.springframework.boot.autoconfigure.web.ServerProperties;
+import org.springframework.boot.context.properties.EnableConfigurationProperties;
+import org.springframework.boot.test.EnvironmentTestUtils;
+import org.springframework.context.annotation.AnnotationConfigApplicationContext;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.validation.Validator;
+
+import static org.junit.Assert.assertEquals;
+
+/**
+ * Tests for {@link SamplePropertyValidationApplication}.
+ *
+ * @author Lucas Saldanha
+ */
+public class SamplePropertyValidationApplicationTests {
+
+ private final AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
+
+ @Test
+ public void testBindingValidProperties() {
+ this.context.register(TestConfiguration.class);
+ EnvironmentTestUtils.addEnvironment(this.context, "host:192.168.0.1");
+ EnvironmentTestUtils.addEnvironment(this.context, "port:8080");
+ this.context.refresh();
+
+ assertEquals(1, this.context.getBeanNamesForType(SampleProperties.class).length);
+ SampleProperties properties = this.context.getBean(SampleProperties.class);
+ assertEquals("192.168.0.1", properties.getHost());
+ assertEquals(8080, (int) properties.getPort());
+ }
+
+ @Test(expected = BeanCreationException.class)
+ public void testBindingInvalidProperties() {
+ this.context.register(TestConfiguration.class);
+ EnvironmentTestUtils.addEnvironment(this.context, "host:xxxxxx");
+ EnvironmentTestUtils.addEnvironment(this.context, "port:8080");
+ this.context.refresh();
+ }
+
+ @Test
+ public void testBindingValidPropertiesWithMultipleConfigurationPropertiesClasses() {
+ this.context.register(TestConfiguration.class);
+ this.context.register(ServerProperties.class);
+ EnvironmentTestUtils.addEnvironment(this.context, "host:192.168.0.1");
+ EnvironmentTestUtils.addEnvironment(this.context, "port:8080");
+ this.context.refresh();
+
+ assertEquals(1, this.context.getBeanNamesForType(SampleProperties.class).length);
+ SampleProperties properties = this.context.getBean(SampleProperties.class);
+ assertEquals("192.168.0.1", properties.getHost());
+ assertEquals(8080, (int) properties.getPort());
+ }
+
+ @Configuration
+ @EnableConfigurationProperties(SampleProperties.class)
+ protected static class TestConfiguration {
+
+ @Bean
+ public Validator configurationPropertiesValidator() {
+ return new ConfigurationPropertiesValidator();
+ }
+ }
+
+}
diff --git a/spring-boot-samples/spring-boot-sample-property-validation/src/test/resources/application.properties b/spring-boot-samples/spring-boot-sample-property-validation/src/test/resources/application.properties
new file mode 100644
index 0000000000..2a20a86341
--- /dev/null
+++ b/spring-boot-samples/spring-boot-sample-property-validation/src/test/resources/application.properties
@@ -0,0 +1,2 @@
+host: 192.168.0.1
+port: 8080
\ No newline at end of file