Search code examples
spring-data-rest

Spring Data Rest: 500 error when 4xx expected


Given the following entities, when I "post" a new entity of "TrainerProfile" and miss some of the @NotNull parameters in "Location", I get a 500 together with a stack trace packed into the JSON instead of a 400 and useful information on what went wrong.

@Entity
public class TrainerProfile {
    ...

    @NotNull
    @OneToOne(cascade = CascadeType.ALL)
    private Location location;

}

@Entity
public class Location {

    @Id @GeneratedValue
    private Long id;

    @NotNull
    private String zipcode;

    @NotNull
    private String city;

    @NotNull
    private String country;

}

When I post this data to the API

{
  ...
  "location": {
    "zipcode": "10000"
  }
}

I see the following logs:

javax.validation.ConstraintViolationException: Validation failed for classes [training.edit.provider.model.Location] during persist time for groups [javax.validation.groups.Default, ]
List of constraint violations:[
    ConstraintViolationImpl{interpolatedMessage='must not be null', propertyPath=city, rootBeanClass=class training.edit.provider.model.Location, messageTemplate='{javax.validation.constraints.NotNull.message}'}
    ConstraintViolationImpl{interpolatedMessage='must not be null', propertyPath=country, rootBeanClass=class training.edit.provider.model.Location, messageTemplate='{javax.validation.constraints.NotNull.message}'}
]

But the REST client sees this:

< HTTP/1.1 500
< Vary: Origin
< Vary: Access-Control-Request-Method
< Vary: Access-Control-Request-Headers
< Access-Control-Allow-Origin: http://localhost:4200
< Access-Control-Allow-Credentials: true
< X-Content-Type-Options: nosniff
< X-XSS-Protection: 1; mode=block
< Cache-Control: no-cache, no-store, max-age=0, must-revalidate
< Pragma: no-cache
< Expires: 0
< X-Frame-Options: DENY
< Content-Type: application/json
< Transfer-Encoding: chunked
< Date: Tue, 16 Jun 2020 09:33:16 GMT
< Connection: close
<
{"timestamp":"2020-06-16T09:33:16.605+0000","status":500,"error":"Internal Server Error","message":"Could not commit JPA transaction; nested exception is javax.persistence.RollbackException: Error while committing the transaction","trace":"org.springframework.transaction.TransactionSystemException: Could not commit JPA transaction; nested exception is javax.persistence.RollbackException: Error while committing the transaction\n\tat org.springframework.orm.jpa.JpaTransactionManager.doCommit(JpaTransactionManager.java:543)\n...

The text is way longer but I spare you the rest.

I configured validation like this:

@Configuration
@RequiredArgsConstructor(onConstructor = @__(@Autowired))
public class RestConfiguration implements RepositoryRestConfigurer {

    private final @NonNull Validator validator;

    private final @NonNull UriToIdConverter converter;

    @Override
    public void configureConversionService(ConfigurableConversionService conversionService) {
        RepositoryRestConfigurer.super.configureConversionService(conversionService);
        conversionService.addConverter(converter);
    }

    @Override
    public void configureValidatingRepositoryEventListener(ValidatingRepositoryEventListener validatingListener) {
       validatingListener.addValidator("afterCreate", validator);
       validatingListener.addValidator("beforeCreate", validator);
       validatingListener.addValidator("afterSave", validator);
       validatingListener.addValidator("beforeSave", validator);
    }

}

How can I get a proper error message in this case? When I post something that doesn't work for "TrainerProfile" then I get a proper message and error code, but not for the nested object.


Solution

  • Intermediate Solution

    Each Spring Data REST request issue a transaction. The outter exception RollbackException gives you a 500 response, even though this exception actually comes from nested validation failure ConstraintViolationException. An working but not nice solution should be have a ResponseEntityExceptionHandler to extract the nested exception, as the auth0 example. The code is here.

    Suggestion

    Just don't use Spring Data REST where it doesn't fit your requirements.

    as said by Spring Data REST leader Oliver Drotbohm in this answer.

    A RESTful API should work for aggregate. Aggregate is a DDD concept. Aggregate doesn't work well with relational database. Try No-SQL database such as Mongo DB. The starting point to learn DDD is Oliver's talk.