diff --git a/spring-mvc-java/pom.xml b/spring-mvc-java/pom.xml
index ef18cef3e0..0f6dbfbd98 100644
--- a/spring-mvc-java/pom.xml
+++ b/spring-mvc-java/pom.xml
@@ -166,7 +166,28 @@
poi-ooxml
${poi.version}
-
+
+
+
+ javax.validation
+ validation-api
+ 1.1.0.Final
+
+
+ org.hibernate
+ hibernate-validator
+ 5.1.2.Final
+
+
+ javax.el
+ javax.el-api
+ 2.2.4
+
+
+ org.glassfish.web
+ javax.el
+ 2.2.4
+
diff --git a/spring-mvc-java/src/main/java/com/baeldung/customvalidator/ContactNumberConstraint.java b/spring-mvc-java/src/main/java/com/baeldung/customvalidator/ContactNumberConstraint.java
new file mode 100644
index 0000000000..2fba2720c3
--- /dev/null
+++ b/spring-mvc-java/src/main/java/com/baeldung/customvalidator/ContactNumberConstraint.java
@@ -0,0 +1,22 @@
+package com.baeldung.customvalidator;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+import javax.validation.Constraint;
+import javax.validation.Payload;
+
+@Documented
+@Constraint(validatedBy = ContactNumberValidator.class)
+@Target( { ElementType.METHOD, ElementType.FIELD })
+@Retention(RetentionPolicy.RUNTIME)
+public @interface ContactNumberConstraint {
+
+ String message() default "Invalid phone number";
+ Class>[] groups() default {};
+ Class extends Payload>[] payload() default {};
+
+}
diff --git a/spring-mvc-java/src/main/java/com/baeldung/customvalidator/ContactNumberValidator.java b/spring-mvc-java/src/main/java/com/baeldung/customvalidator/ContactNumberValidator.java
new file mode 100644
index 0000000000..a7eb7a9df4
--- /dev/null
+++ b/spring-mvc-java/src/main/java/com/baeldung/customvalidator/ContactNumberValidator.java
@@ -0,0 +1,19 @@
+package com.baeldung.customvalidator;
+
+import javax.validation.ConstraintValidator;
+import javax.validation.ConstraintValidatorContext;
+
+public class ContactNumberValidator implements ConstraintValidator {
+
+ @Override
+ public void initialize(ContactNumberConstraint contactNumber) {}
+
+ @Override
+ public boolean isValid(String contactField, ConstraintValidatorContext cxt) {
+ if(contactField == null) {
+ return false;
+ }
+ return contactField.matches("[0-9]+") && (contactField.length() > 8) && (contactField.length() < 14);
+ }
+
+}
diff --git a/spring-mvc-java/src/main/java/com/baeldung/model/ValidatedPhone.java b/spring-mvc-java/src/main/java/com/baeldung/model/ValidatedPhone.java
new file mode 100644
index 0000000000..f860394707
--- /dev/null
+++ b/spring-mvc-java/src/main/java/com/baeldung/model/ValidatedPhone.java
@@ -0,0 +1,18 @@
+package com.baeldung.model;
+
+import com.baeldung.customvalidator.ContactNumberConstraint;
+
+public class ValidatedPhone {
+
+ @ContactNumberConstraint
+ private String phone;
+
+ public String getPhone() {
+ return phone;
+ }
+
+ public void setPhone(String phone) {
+ this.phone = phone;
+ }
+
+}
diff --git a/spring-mvc-java/src/main/java/com/baeldung/web/controller/ValidatedPhoneController.java b/spring-mvc-java/src/main/java/com/baeldung/web/controller/ValidatedPhoneController.java
new file mode 100644
index 0000000000..70b151e066
--- /dev/null
+++ b/spring-mvc-java/src/main/java/com/baeldung/web/controller/ValidatedPhoneController.java
@@ -0,0 +1,35 @@
+package com.baeldung.web.controller;
+
+import javax.validation.Valid;
+
+import org.springframework.stereotype.Controller;
+import org.springframework.ui.Model;
+import org.springframework.validation.BindingResult;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RequestMethod;
+import org.springframework.web.servlet.config.annotation.EnableWebMvc;
+
+import com.baeldung.model.ValidatedPhone;
+
+@Controller
+@EnableWebMvc
+public class ValidatedPhoneController {
+
+ @RequestMapping(value="/validatePhone", method=RequestMethod.GET)
+ public String loadFormPage(Model m) {
+ m.addAttribute("validatedPhone", new ValidatedPhone());
+ return "phoneHome";
+ }
+
+ @RequestMapping(value="/addValidatePhone", method=RequestMethod.POST)
+ public String submitForm(@Valid ValidatedPhone validatedPhone, BindingResult result, Model m) {
+ if(result.hasErrors()) {
+ return "phoneHome";
+ }
+
+ m.addAttribute("message", "Successfully saved phone: " + validatedPhone.toString());
+ return "phoneHome";
+ }
+
+
+}
diff --git a/spring-mvc-java/src/main/webapp/WEB-INF/mvc-servlet.xml b/spring-mvc-java/src/main/webapp/WEB-INF/mvc-servlet.xml
index 4ba9642448..b0a4d4892a 100644
--- a/spring-mvc-java/src/main/webapp/WEB-INF/mvc-servlet.xml
+++ b/spring-mvc-java/src/main/webapp/WEB-INF/mvc-servlet.xml
@@ -1,6 +1,14 @@
-
-
+
+
+
\ No newline at end of file
diff --git a/spring-mvc-java/src/main/webapp/WEB-INF/view/phoneHome.jsp b/spring-mvc-java/src/main/webapp/WEB-INF/view/phoneHome.jsp
new file mode 100644
index 0000000000..b873e9bc5f
--- /dev/null
+++ b/spring-mvc-java/src/main/webapp/WEB-INF/view/phoneHome.jsp
@@ -0,0 +1,38 @@
+<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
+<%@ taglib prefix="form" uri="http://www.springframework.org/tags/form" %>
+
+
+
+
+ Sample Form
+
+
+
+
+
+
+
Phone Number
+
${message}
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/spring-mvc-java/src/test/java/com/baeldung/web/controller/CustomMVCValidatorTest.java b/spring-mvc-java/src/test/java/com/baeldung/web/controller/CustomMVCValidatorTest.java
new file mode 100644
index 0000000000..069cc8e141
--- /dev/null
+++ b/spring-mvc-java/src/test/java/com/baeldung/web/controller/CustomMVCValidatorTest.java
@@ -0,0 +1,41 @@
+package com.baeldung.web.controller;
+
+import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
+import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print;
+import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.model;
+import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
+import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.view;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.springframework.http.MediaType;
+import org.springframework.test.web.servlet.MockMvc;
+import org.springframework.test.web.servlet.request.MockMvcRequestBuilders;
+import org.springframework.test.web.servlet.setup.MockMvcBuilders;
+
+public class CustomMVCValidatorTest {
+
+ private MockMvc mockMvc;
+
+ @Before
+ public void setup(){
+ this.mockMvc = MockMvcBuilders.standaloneSetup(new ValidatedPhoneController()).build();
+ }
+
+ @Test
+ public void givenPhonePageUri_whenMockMvc_thenReturnsPhonePage() throws Exception{
+ this.mockMvc.perform(get("/validatePhone")).andExpect(view().name("phoneHome"));
+ }
+
+ @Test
+ public void givenPhoneURIWithPostAndFormData_whenMockMVC_thenVerifyErrorResponse() throws Exception {
+ this.mockMvc.perform(MockMvcRequestBuilders.post("/addValidatePhone").
+ accept(MediaType.TEXT_HTML).
+ param("phoneInput", "123")).
+ andExpect(model().attributeHasFieldErrorCode("validatedPhone", "phone","ContactNumberConstraint")).
+ andExpect(view().name("phoneHome")).
+ andExpect(status().isOk()).
+ andDo(print());
+ }
+
+}