diff --git a/spring-boot-modules/spring-boot-mvc/pom.xml b/spring-boot-modules/spring-boot-mvc/pom.xml
index 369bcf799b..a251c5049b 100644
--- a/spring-boot-modules/spring-boot-mvc/pom.xml
+++ b/spring-boot-modules/spring-boot-mvc/pom.xml
@@ -91,6 +91,10 @@
aspectjweaver
${aspectjweaver.version}
+
+ org.projectlombok
+ lombok
+
diff --git a/spring-boot-modules/spring-boot-mvc/src/main/java/com/baeldung/restvalidation/RestValidationApplication.java b/spring-boot-modules/spring-boot-mvc/src/main/java/com/baeldung/restvalidation/RestValidationApplication.java
new file mode 100644
index 0000000000..b335aecac2
--- /dev/null
+++ b/spring-boot-modules/spring-boot-mvc/src/main/java/com/baeldung/restvalidation/RestValidationApplication.java
@@ -0,0 +1,15 @@
+package com.baeldung.restvalidation;
+
+import org.springframework.boot.SpringApplication;
+import org.springframework.boot.autoconfigure.SpringBootApplication;
+import org.springframework.context.annotation.ComponentScan;
+
+@SpringBootApplication
+@ComponentScan(basePackages = { "com.baeldung.restvalidation" })
+public class RestValidationApplication {
+
+ public static void main(String[] args) {
+ SpringApplication.run(RestValidationApplication.class, args);
+ }
+
+}
\ No newline at end of file
diff --git a/spring-boot-modules/spring-boot-mvc/src/main/java/com/baeldung/restvalidation/config/MessageConfig.java b/spring-boot-modules/spring-boot-mvc/src/main/java/com/baeldung/restvalidation/config/MessageConfig.java
new file mode 100644
index 0000000000..e37e2c9d78
--- /dev/null
+++ b/spring-boot-modules/spring-boot-mvc/src/main/java/com/baeldung/restvalidation/config/MessageConfig.java
@@ -0,0 +1,38 @@
+package com.baeldung.restvalidation.config;
+
+import javax.validation.MessageInterpolator;
+
+import org.hibernate.validator.messageinterpolation.ResourceBundleMessageInterpolator;
+import org.springframework.context.MessageSource;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.context.support.ReloadableResourceBundleMessageSource;
+import org.springframework.validation.beanvalidation.LocalValidatorFactoryBean;
+import org.springframework.validation.beanvalidation.MessageSourceResourceBundleLocator;
+
+@Configuration
+public class MessageConfig {
+
+ @Bean
+ public MessageSource messageSource() {
+ ReloadableResourceBundleMessageSource messageSource = new ReloadableResourceBundleMessageSource();
+ messageSource.setBasename("classpath:CustomValidationMessages");
+ messageSource.setDefaultEncoding("UTF-8");
+ return messageSource;
+ }
+
+ @Bean
+ public MessageInterpolator getMessageInterpolator(MessageSource messageSource) {
+ MessageSourceResourceBundleLocator resourceBundleLocator = new MessageSourceResourceBundleLocator(messageSource);
+ ResourceBundleMessageInterpolator messageInterpolator = new ResourceBundleMessageInterpolator(resourceBundleLocator);
+ return new RecursiveLocaleContextMessageInterpolator(messageInterpolator);
+ }
+
+ @Bean
+ public LocalValidatorFactoryBean getValidator(MessageInterpolator messageInterpolator) {
+ LocalValidatorFactoryBean bean = new LocalValidatorFactoryBean();
+ bean.setMessageInterpolator(messageInterpolator);
+ return bean;
+ }
+
+}
\ No newline at end of file
diff --git a/spring-boot-modules/spring-boot-mvc/src/main/java/com/baeldung/restvalidation/config/RecursiveLocaleContextMessageInterpolator.java b/spring-boot-modules/spring-boot-mvc/src/main/java/com/baeldung/restvalidation/config/RecursiveLocaleContextMessageInterpolator.java
new file mode 100644
index 0000000000..003a3d79b0
--- /dev/null
+++ b/spring-boot-modules/spring-boot-mvc/src/main/java/com/baeldung/restvalidation/config/RecursiveLocaleContextMessageInterpolator.java
@@ -0,0 +1,36 @@
+package com.baeldung.restvalidation.config;
+
+import java.util.Locale;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+import javax.validation.MessageInterpolator;
+
+import org.hibernate.validator.messageinterpolation.AbstractMessageInterpolator;
+import org.hibernate.validator.messageinterpolation.ResourceBundleMessageInterpolator;
+
+public class RecursiveLocaleContextMessageInterpolator extends AbstractMessageInterpolator {
+
+ private static final Pattern PATTERN_PLACEHOLDER = Pattern.compile("\\{([^}]+)\\}");
+
+ private final MessageInterpolator interpolator;
+
+ public RecursiveLocaleContextMessageInterpolator(ResourceBundleMessageInterpolator interpolator) {
+ this.interpolator = interpolator;
+ }
+
+ @Override
+ public String interpolate(MessageInterpolator.Context context, Locale locale, String message) {
+ int level = 0;
+ while (containsPlaceholder(message) && (level++ < 2)) {
+ message = this.interpolator.interpolate(message, context, locale);
+ }
+ return message;
+ }
+
+ private boolean containsPlaceholder(String code) {
+ Matcher matcher = PATTERN_PLACEHOLDER.matcher(code);
+ return matcher.find();
+ }
+
+}
\ No newline at end of file
diff --git a/spring-boot-modules/spring-boot-mvc/src/main/java/com/baeldung/restvalidation/response/InputFieldError.java b/spring-boot-modules/spring-boot-mvc/src/main/java/com/baeldung/restvalidation/response/InputFieldError.java
new file mode 100644
index 0000000000..06bd764d97
--- /dev/null
+++ b/spring-boot-modules/spring-boot-mvc/src/main/java/com/baeldung/restvalidation/response/InputFieldError.java
@@ -0,0 +1,12 @@
+package com.baeldung.restvalidation.response;
+
+import lombok.*;
+
+@Data
+@AllArgsConstructor
+@NoArgsConstructor
+public class InputFieldError {
+ private String field;
+ private String message;
+
+}
\ No newline at end of file
diff --git a/spring-boot-modules/spring-boot-mvc/src/main/java/com/baeldung/restvalidation/response/UpdateUserResponse.java b/spring-boot-modules/spring-boot-mvc/src/main/java/com/baeldung/restvalidation/response/UpdateUserResponse.java
new file mode 100644
index 0000000000..7eb400cd2c
--- /dev/null
+++ b/spring-boot-modules/spring-boot-mvc/src/main/java/com/baeldung/restvalidation/response/UpdateUserResponse.java
@@ -0,0 +1,17 @@
+package com.baeldung.restvalidation.response;
+
+import com.fasterxml.jackson.annotation.JsonInclude;
+
+import java.util.List;
+
+import lombok.*;
+
+@Data
+@AllArgsConstructor
+@NoArgsConstructor
+@JsonInclude(JsonInclude.Include.NON_NULL)
+public class UpdateUserResponse {
+
+ private List fieldErrors;
+
+}
diff --git a/spring-boot-modules/spring-boot-mvc/src/main/java/com/baeldung/restvalidation/service1/User.java b/spring-boot-modules/spring-boot-mvc/src/main/java/com/baeldung/restvalidation/service1/User.java
new file mode 100644
index 0000000000..f9a7d1a9b5
--- /dev/null
+++ b/spring-boot-modules/spring-boot-mvc/src/main/java/com/baeldung/restvalidation/service1/User.java
@@ -0,0 +1,15 @@
+package com.baeldung.restvalidation.service1;
+
+import javax.validation.constraints.NotEmpty;
+
+import lombok.*;
+
+@Data
+@AllArgsConstructor
+@NoArgsConstructor
+public class User {
+
+ @NotEmpty
+ private String email;
+
+}
\ No newline at end of file
diff --git a/spring-boot-modules/spring-boot-mvc/src/main/java/com/baeldung/restvalidation/service1/UserService1.java b/spring-boot-modules/spring-boot-mvc/src/main/java/com/baeldung/restvalidation/service1/UserService1.java
new file mode 100644
index 0000000000..790b5031e6
--- /dev/null
+++ b/spring-boot-modules/spring-boot-mvc/src/main/java/com/baeldung/restvalidation/service1/UserService1.java
@@ -0,0 +1,36 @@
+package com.baeldung.restvalidation.service1;
+
+import org.springframework.http.*;
+import org.springframework.validation.BindingResult;
+import org.springframework.web.bind.annotation.*;
+
+import java.util.List;
+import java.util.stream.Collectors;
+
+import javax.validation.Valid;
+
+import com.baeldung.restvalidation.response.InputFieldError;
+import com.baeldung.restvalidation.response.UpdateUserResponse;
+
+@RestController
+public class UserService1 {
+
+ @PutMapping(value = "/user1", produces = MediaType.APPLICATION_JSON_VALUE)
+ public ResponseEntity updateUser(@RequestBody @Valid User user,
+ BindingResult bindingResult) {
+ if (bindingResult.hasFieldErrors()) {
+
+ List fieldErrorList = bindingResult.getFieldErrors().stream()
+ .map(error -> new InputFieldError(error.getField(), error.getDefaultMessage()))
+ .collect(Collectors.toList());
+
+ UpdateUserResponse updateResponse = new UpdateUserResponse(fieldErrorList);
+ return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(updateResponse);
+ }
+ else {
+ // Update logic...
+ return ResponseEntity.status(HttpStatus.OK).build();
+ }
+ }
+
+}
\ No newline at end of file
diff --git a/spring-boot-modules/spring-boot-mvc/src/main/java/com/baeldung/restvalidation/service2/User.java b/spring-boot-modules/spring-boot-mvc/src/main/java/com/baeldung/restvalidation/service2/User.java
new file mode 100644
index 0000000000..a2e567766c
--- /dev/null
+++ b/spring-boot-modules/spring-boot-mvc/src/main/java/com/baeldung/restvalidation/service2/User.java
@@ -0,0 +1,15 @@
+package com.baeldung.restvalidation.service2;
+
+import javax.validation.constraints.NotEmpty;
+
+import lombok.*;
+
+@Data
+@AllArgsConstructor
+@NoArgsConstructor
+public class User {
+
+ @NotEmpty(message = "{validation.email.notEmpty}")
+ private String email;
+
+}
\ No newline at end of file
diff --git a/spring-boot-modules/spring-boot-mvc/src/main/java/com/baeldung/restvalidation/service2/UserService2.java b/spring-boot-modules/spring-boot-mvc/src/main/java/com/baeldung/restvalidation/service2/UserService2.java
new file mode 100644
index 0000000000..4593a2b1bd
--- /dev/null
+++ b/spring-boot-modules/spring-boot-mvc/src/main/java/com/baeldung/restvalidation/service2/UserService2.java
@@ -0,0 +1,40 @@
+package com.baeldung.restvalidation.service2;
+
+import java.util.List;
+import java.util.stream.Collectors;
+
+import javax.validation.Valid;
+
+import org.springframework.http.HttpStatus;
+import org.springframework.http.MediaType;
+import org.springframework.http.ResponseEntity;
+import org.springframework.validation.BindingResult;
+import org.springframework.web.bind.annotation.PutMapping;
+import org.springframework.web.bind.annotation.RequestBody;
+import org.springframework.web.bind.annotation.RestController;
+
+import com.baeldung.restvalidation.response.InputFieldError;
+import com.baeldung.restvalidation.response.UpdateUserResponse;
+
+@RestController
+public class UserService2 {
+
+ @PutMapping(value = "/user2", produces = MediaType.APPLICATION_JSON_VALUE)
+ public ResponseEntity updateUser(@RequestBody @Valid User user,
+ BindingResult bindingResult) {
+ if (bindingResult.hasFieldErrors()) {
+
+ List fieldErrorList = bindingResult.getFieldErrors().stream()
+ .map(error -> new InputFieldError(error.getField(), error.getDefaultMessage()))
+ .collect(Collectors.toList());
+
+ UpdateUserResponse updateResponse = new UpdateUserResponse(fieldErrorList);
+ return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(updateResponse);
+ }
+ else {
+ // Update logic...
+ return ResponseEntity.status(HttpStatus.OK).build();
+ }
+ }
+
+}
\ No newline at end of file
diff --git a/spring-boot-modules/spring-boot-mvc/src/main/java/com/baeldung/restvalidation/service3/FieldNotEmpty.java b/spring-boot-modules/spring-boot-mvc/src/main/java/com/baeldung/restvalidation/service3/FieldNotEmpty.java
new file mode 100644
index 0000000000..93c7052d5b
--- /dev/null
+++ b/spring-boot-modules/spring-boot-mvc/src/main/java/com/baeldung/restvalidation/service3/FieldNotEmpty.java
@@ -0,0 +1,32 @@
+package com.baeldung.restvalidation.service3;
+
+import static java.lang.annotation.ElementType.ANNOTATION_TYPE;
+import static java.lang.annotation.ElementType.CONSTRUCTOR;
+import static java.lang.annotation.ElementType.FIELD;
+import static java.lang.annotation.ElementType.METHOD;
+import static java.lang.annotation.ElementType.PARAMETER;
+import static java.lang.annotation.ElementType.TYPE_USE;
+import static java.lang.annotation.RetentionPolicy.RUNTIME;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.Retention;
+import java.lang.annotation.Target;
+
+import javax.validation.Constraint;
+import javax.validation.Payload;
+
+@Documented
+@Target({ METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER, TYPE_USE })
+@Retention(RUNTIME)
+@Constraint(validatedBy = { FieldNotEmptyValidator.class })
+public @interface FieldNotEmpty {
+
+ String message() default "{validation.notEmpty}";
+
+ String field() default "Field";
+
+ Class>[] groups() default {};
+
+ Class extends Payload>[] payload() default {};
+
+}
diff --git a/spring-boot-modules/spring-boot-mvc/src/main/java/com/baeldung/restvalidation/service3/FieldNotEmptyValidator.java b/spring-boot-modules/spring-boot-mvc/src/main/java/com/baeldung/restvalidation/service3/FieldNotEmptyValidator.java
new file mode 100644
index 0000000000..356efc590b
--- /dev/null
+++ b/spring-boot-modules/spring-boot-mvc/src/main/java/com/baeldung/restvalidation/service3/FieldNotEmptyValidator.java
@@ -0,0 +1,16 @@
+package com.baeldung.restvalidation.service3;
+
+import javax.validation.ConstraintValidator;
+import javax.validation.ConstraintValidatorContext;
+
+public class FieldNotEmptyValidator implements ConstraintValidator {
+
+ private String message;
+ private String field;
+
+ @Override
+ public boolean isValid(Object value, ConstraintValidatorContext context) {
+ return (value != null && !value.toString().trim().isEmpty());
+ }
+
+}
\ No newline at end of file
diff --git a/spring-boot-modules/spring-boot-mvc/src/main/java/com/baeldung/restvalidation/service3/User.java b/spring-boot-modules/spring-boot-mvc/src/main/java/com/baeldung/restvalidation/service3/User.java
new file mode 100644
index 0000000000..97b38feba4
--- /dev/null
+++ b/spring-boot-modules/spring-boot-mvc/src/main/java/com/baeldung/restvalidation/service3/User.java
@@ -0,0 +1,13 @@
+package com.baeldung.restvalidation.service3;
+
+import lombok.*;
+
+@Data
+@AllArgsConstructor
+@NoArgsConstructor
+public class User {
+
+ @FieldNotEmpty(message = "{validation.notEmpty}", field = "{field.personalEmail}")
+ private String email;
+
+}
\ No newline at end of file
diff --git a/spring-boot-modules/spring-boot-mvc/src/main/java/com/baeldung/restvalidation/service3/UserService3.java b/spring-boot-modules/spring-boot-mvc/src/main/java/com/baeldung/restvalidation/service3/UserService3.java
new file mode 100644
index 0000000000..e506b63d8e
--- /dev/null
+++ b/spring-boot-modules/spring-boot-mvc/src/main/java/com/baeldung/restvalidation/service3/UserService3.java
@@ -0,0 +1,40 @@
+package com.baeldung.restvalidation.service3;
+
+import java.util.List;
+import java.util.stream.Collectors;
+
+import javax.validation.Valid;
+
+import org.springframework.http.HttpStatus;
+import org.springframework.http.MediaType;
+import org.springframework.http.ResponseEntity;
+import org.springframework.validation.BindingResult;
+import org.springframework.web.bind.annotation.PutMapping;
+import org.springframework.web.bind.annotation.RequestBody;
+import org.springframework.web.bind.annotation.RestController;
+
+import com.baeldung.restvalidation.response.InputFieldError;
+import com.baeldung.restvalidation.response.UpdateUserResponse;
+
+@RestController
+public class UserService3 {
+
+ @PutMapping(value = "/user3", produces = MediaType.APPLICATION_JSON_VALUE)
+ public ResponseEntity updateUser(@RequestBody @Valid User user,
+ BindingResult bindingResult) {
+ if (bindingResult.hasFieldErrors()) {
+
+ List fieldErrorList = bindingResult.getFieldErrors().stream()
+ .map(error -> new InputFieldError(error.getField(), error.getDefaultMessage()))
+ .collect(Collectors.toList());
+
+ UpdateUserResponse updateResponse = new UpdateUserResponse(fieldErrorList);
+ return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(updateResponse);
+ }
+ else {
+ // Update logic...
+ return ResponseEntity.status(HttpStatus.OK).build();
+ }
+ }
+
+}
\ No newline at end of file
diff --git a/spring-boot-modules/spring-boot-mvc/src/main/resources/CustomValidationMessages.properties b/spring-boot-modules/spring-boot-mvc/src/main/resources/CustomValidationMessages.properties
new file mode 100644
index 0000000000..58cabc30c9
--- /dev/null
+++ b/spring-boot-modules/spring-boot-mvc/src/main/resources/CustomValidationMessages.properties
@@ -0,0 +1,3 @@
+field.personalEmail=Personal Email
+validation.notEmpty={field} cannot be empty
+validation.email.notEmpty=Email cannot be empty
\ No newline at end of file
diff --git a/spring-boot-modules/spring-boot-mvc/src/main/resources/CustomValidationMessages_zh.properties b/spring-boot-modules/spring-boot-mvc/src/main/resources/CustomValidationMessages_zh.properties
new file mode 100644
index 0000000000..b4fbe909bb
--- /dev/null
+++ b/spring-boot-modules/spring-boot-mvc/src/main/resources/CustomValidationMessages_zh.properties
@@ -0,0 +1,3 @@
+field.personalEmail=個人電郵
+validation.notEmpty={field}不能是空白
+validation.email.notEmpty=電郵不能留空
\ No newline at end of file
diff --git a/spring-boot-modules/spring-boot-mvc/src/main/resources/ValidationMessages.properties b/spring-boot-modules/spring-boot-mvc/src/main/resources/ValidationMessages.properties
new file mode 100644
index 0000000000..90d3c88a8f
--- /dev/null
+++ b/spring-boot-modules/spring-boot-mvc/src/main/resources/ValidationMessages.properties
@@ -0,0 +1 @@
+javax.validation.constraints.NotEmpty.message=The field cannot be empty
\ No newline at end of file
diff --git a/spring-boot-modules/spring-boot-mvc/src/main/resources/ValidationMessages_zh.properties b/spring-boot-modules/spring-boot-mvc/src/main/resources/ValidationMessages_zh.properties
new file mode 100644
index 0000000000..04f415911c
--- /dev/null
+++ b/spring-boot-modules/spring-boot-mvc/src/main/resources/ValidationMessages_zh.properties
@@ -0,0 +1 @@
+javax.validation.constraints.NotEmpty.message=本欄不能留空
\ No newline at end of file
diff --git a/spring-boot-modules/spring-boot-mvc/src/test/java/com/baeldung/restvalidation/service1/UserService1IntegrationTest.java b/spring-boot-modules/spring-boot-mvc/src/test/java/com/baeldung/restvalidation/service1/UserService1IntegrationTest.java
new file mode 100644
index 0000000000..969a6c17a6
--- /dev/null
+++ b/spring-boot-modules/spring-boot-mvc/src/test/java/com/baeldung/restvalidation/service1/UserService1IntegrationTest.java
@@ -0,0 +1,75 @@
+package com.baeldung.restvalidation.service1;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+import java.util.Objects;
+
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.ExtendWith;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.test.context.SpringBootTest;
+import org.springframework.boot.test.web.client.TestRestTemplate;
+import org.springframework.http.*;
+import org.springframework.test.context.junit.jupiter.SpringExtension;
+
+import com.baeldung.restvalidation.RestValidationApplication;
+import com.baeldung.restvalidation.response.InputFieldError;
+import com.baeldung.restvalidation.response.UpdateUserResponse;
+
+@ExtendWith(SpringExtension.class)
+@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT, classes = RestValidationApplication.class)
+class UserService1IntegrationTest {
+
+ @Autowired
+ private TestRestTemplate restTemplate;
+
+ @Test
+ void whenUpdateValidEmail_thenReturnsOK() {
+
+ // When
+ ResponseEntity responseEntity = updateUser(new User("test@email.com"), null);
+
+ // Then
+ assertEquals(HttpStatus.OK, responseEntity.getStatusCode());
+ }
+
+ @Test
+ void whenUpdateEmptyEmail_thenReturnsErrorMessageInEnglish() {
+
+ // When
+ ResponseEntity responseEntity = updateUser(new User(""), null);
+
+ // Then
+ InputFieldError error = responseEntity.getBody().getFieldErrors().get(0);
+ assertEquals(HttpStatus.BAD_REQUEST, responseEntity.getStatusCode());
+ assertEquals("The field cannot be empty", error.getMessage());
+ }
+
+ @Test
+ void whenUpdateEmptyEmailWithLanguageHeaderEqualsToZh_thenReturnsErrorMessageInChinese() {
+
+ // When
+ ResponseEntity responseEntity = updateUser(new User(""), "zh-tw");
+
+ // Then
+ InputFieldError error = responseEntity.getBody().getFieldErrors().get(0);
+ assertEquals(HttpStatus.BAD_REQUEST, responseEntity.getStatusCode());
+ assertEquals("本欄不能留空", error.getMessage());
+ }
+
+ private ResponseEntity updateUser(User user, String language) {
+
+ HttpHeaders headers = new HttpHeaders();
+ if (Objects.nonNull(language)) {
+ headers.set(HttpHeaders.ACCEPT_LANGUAGE, language);
+ }
+
+ return restTemplate.exchange(
+ "/user1",
+ HttpMethod.PUT,
+ new HttpEntity<>(user, headers),
+ UpdateUserResponse.class
+ );
+ }
+
+}
\ No newline at end of file
diff --git a/spring-boot-modules/spring-boot-mvc/src/test/java/com/baeldung/restvalidation/service2/UserService2IntegrationTest.java b/spring-boot-modules/spring-boot-mvc/src/test/java/com/baeldung/restvalidation/service2/UserService2IntegrationTest.java
new file mode 100644
index 0000000000..3e92229d7b
--- /dev/null
+++ b/spring-boot-modules/spring-boot-mvc/src/test/java/com/baeldung/restvalidation/service2/UserService2IntegrationTest.java
@@ -0,0 +1,75 @@
+package com.baeldung.restvalidation.service2;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+import java.util.Objects;
+
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.ExtendWith;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.test.context.SpringBootTest;
+import org.springframework.boot.test.web.client.TestRestTemplate;
+import org.springframework.http.*;
+import org.springframework.test.context.junit.jupiter.SpringExtension;
+
+import com.baeldung.restvalidation.RestValidationApplication;
+import com.baeldung.restvalidation.response.InputFieldError;
+import com.baeldung.restvalidation.response.UpdateUserResponse;
+
+@ExtendWith(SpringExtension.class)
+@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT, classes = RestValidationApplication.class)
+class UserService2IntegrationTest {
+
+ @Autowired
+ private TestRestTemplate restTemplate;
+
+ @Test
+ void whenUpdateValidEmail_thenReturnsOK() {
+
+ // When
+ ResponseEntity responseEntity = updateUser(new User("test@email.com"), null);
+
+ // Then
+ assertEquals(HttpStatus.OK, responseEntity.getStatusCode());
+ }
+
+ @Test
+ void whenUpdateEmptyEmail_thenReturnsErrorMessageInEnglish() {
+
+ // When
+ ResponseEntity responseEntity = updateUser(new User(""), null);
+
+ // Then
+ InputFieldError error = responseEntity.getBody().getFieldErrors().get(0);
+ assertEquals(HttpStatus.BAD_REQUEST, responseEntity.getStatusCode());
+ assertEquals("Email cannot be empty", error.getMessage());
+ }
+
+ @Test
+ void whenUpdateEmptyEmailWithLanguageHeaderEqualsToZh_thenReturnsErrorMessageInChinese() {
+
+ // When
+ ResponseEntity responseEntity = updateUser(new User(""), "zh-tw");
+
+ // Then
+ InputFieldError error = responseEntity.getBody().getFieldErrors().get(0);
+ assertEquals(HttpStatus.BAD_REQUEST, responseEntity.getStatusCode());
+ assertEquals("電郵不能留空", error.getMessage());
+ }
+
+ private ResponseEntity updateUser(User user, String language) {
+
+ HttpHeaders headers = new HttpHeaders();
+ if (Objects.nonNull(language)) {
+ headers.set(HttpHeaders.ACCEPT_LANGUAGE, language);
+ }
+
+ return restTemplate.exchange(
+ "/user2",
+ HttpMethod.PUT,
+ new HttpEntity<>(user, headers),
+ UpdateUserResponse.class
+ );
+ }
+
+}
\ No newline at end of file
diff --git a/spring-boot-modules/spring-boot-mvc/src/test/java/com/baeldung/restvalidation/service3/UserService3IntegrationTest.java b/spring-boot-modules/spring-boot-mvc/src/test/java/com/baeldung/restvalidation/service3/UserService3IntegrationTest.java
new file mode 100644
index 0000000000..8bc4d460ea
--- /dev/null
+++ b/spring-boot-modules/spring-boot-mvc/src/test/java/com/baeldung/restvalidation/service3/UserService3IntegrationTest.java
@@ -0,0 +1,75 @@
+package com.baeldung.restvalidation.service3;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+import java.util.Objects;
+
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.ExtendWith;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.test.context.SpringBootTest;
+import org.springframework.boot.test.web.client.TestRestTemplate;
+import org.springframework.http.*;
+import org.springframework.test.context.junit.jupiter.SpringExtension;
+
+import com.baeldung.restvalidation.RestValidationApplication;
+import com.baeldung.restvalidation.response.InputFieldError;
+import com.baeldung.restvalidation.response.UpdateUserResponse;
+
+@ExtendWith(SpringExtension.class)
+@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT, classes = RestValidationApplication.class)
+class UserService3IntegrationTest {
+
+ @Autowired
+ private TestRestTemplate restTemplate;
+
+ @Test
+ void whenUpdateValidEmail_thenReturnsOK() {
+
+ // When
+ ResponseEntity responseEntity = updateUser(new User("test@email.com"), null);
+
+ // Then
+ assertEquals(HttpStatus.OK, responseEntity.getStatusCode());
+ }
+
+ @Test
+ void whenUpdateEmptyEmail_thenReturnsErrorMessageInEnglish() {
+
+ // When
+ ResponseEntity responseEntity = updateUser(new User(""), null);
+
+ // Then
+ InputFieldError error = responseEntity.getBody().getFieldErrors().get(0);
+ assertEquals(HttpStatus.BAD_REQUEST, responseEntity.getStatusCode());
+ assertEquals("Personal Email cannot be empty", error.getMessage());
+ }
+
+ @Test
+ void whenUpdateEmptyEmailWithLanguageHeaderEqualsToZh_thenReturnsErrorMessageInChinese() {
+
+ // When
+ ResponseEntity responseEntity = updateUser(new User(""), "zh-tw");
+
+ // Then
+ InputFieldError error = responseEntity.getBody().getFieldErrors().get(0);
+ assertEquals(HttpStatus.BAD_REQUEST, responseEntity.getStatusCode());
+ assertEquals("個人電郵不能是空白", error.getMessage());
+ }
+
+ private ResponseEntity updateUser(User user, String language) {
+
+ HttpHeaders headers = new HttpHeaders();
+ if (Objects.nonNull(language)) {
+ headers.set(HttpHeaders.ACCEPT_LANGUAGE, language);
+ }
+
+ return restTemplate.exchange(
+ "/user3",
+ HttpMethod.PUT,
+ new HttpEntity<>(user, headers),
+ UpdateUserResponse.class
+ );
+ }
+
+}
\ No newline at end of file