Background
Spring Data REST is a framework that helps developers to build hypermedia-driven REST web services. It is built on top of the Spring Data project and makes it easy to build hypermedia-driven REST web services that connect to Spring Data repositories – all using HAL as the driving hypermedia type.
In this article, we will look at how to implement validation in Spring Data REST.
Domain Classes
There are two @Entity
classes, Author and Book. Both classes are accompanied by JpaRepository
classes - AuthorRepository and BookRepository.
While Author
and Book
are standard JPA entities, their repositories are annotated with @RepositoryRestResource
to expose them as REST resources.
@RepositoryRestResource
interface AuthorRepository extends JpaRepository<Author, UUID> {
}
@RepositoryRestResource
interface BookRepository extends JpaRepository<Book, UUID> {
}
Validation
We will implement a validation that ensures that Author
in Book
is not INACTIVE
. To do this, we will create a custom validator class, BeforeCreateBookValidator.
class BeforeCreateBookValidator implements Validator {
@Override
public boolean supports(Class<?> clazz) {
return Book.class.isAssignableFrom(clazz);
}
@Override
public void validate(Object target, Errors errors) {
Book book = (Book) target;
if (book.getAuthor().getStatus() == INACTIVE) {
errors.rejectValue("author", "author.inactive", "Author is inactive");
}
}
}
As we can see, the validator class implements Validator
interface and overrides supports
and validate
methods. The supports
method checks if the class is Book
and the validate
method checks if the Author
is INACTIVE
. Next we will inform Spring about our Validator
through BookValidatorConfiguration.
@Configuration
class BookValidatorConfiguration implements RepositoryRestConfigurer {
@Override
public void configureValidatingRepositoryEventListener(ValidatingRepositoryEventListener validatingListener) {
validatingListener.addValidator("beforeCreate", new BeforeCreateBookValidator());
}
}
Now, Spring is aware that the Validator
will be executed before creating a Book
.
Verify Implementation
We will perform a POST
request to create a Book
with an Author
that is INACTIVE
. The request will be rejected with 400 Bad Request
response.
@Import(TestDataRestValidationApplication.class)
@SpringBootTest(webEnvironment = RANDOM_PORT, properties = "spring.jpa.hibernate.ddl-auto=create-drop")
class CreateBookTests {
@Autowired
private TestRestTemplate restClient;
@Test
@DisplayName("When I create a Book with an inactive Author, I should get a Bad Request response")
void inactiveAuthor() {
var body = """
{
"title": "If",
"author": "%s"
}
""".formatted(authorUri());
var response = restClient.exchange("/books", POST, new HttpEntity<>(body, headers()), RepositoryRestErrorResponse.class);
assertThat(response.getStatusCode()).isEqualTo(BAD_REQUEST);
assertThat(response.getBody().getErrors())
.hasSize(1)
.extracting(ValidationError::getMessage)
.containsExactly("Author is inactive");
}
private URI authorUri() {
var body = """
{
"name": "Rudyard Kipling",
"status": "INACTIVE"
}
""";
return restClient.exchange("/authors", POST, new HttpEntity<>(body, headers()), Void.class)
.getHeaders()
.getLocation();
}
}
Full implementation of the test can be found in CreateBookTests.