2

I have a spring boot hibernate MySQL microservice and a web client that accesses the service via REST.

I have realized some database validations via hibernate annotations... e.g. not-null or unique.

Now, when the validation finds an error I get a stacktrace like this:

Caused by: org.springframework.dao.DataIntegrityViolationException: could not execute statement; SQL [n/a]; 
constraint [rahmenvertraege.UK_lla4el5s0eip4nwn8e3sd1trk]; nested exception is
 org.hibernate.exception.ConstraintViolationException: could not execute statement
...
...
...
Caused by: org.hibernate.exception.ConstraintViolationException: could not execute statement
    at org.hibernate.exception.internal.SQLExceptionTypeDelegate.convert(SQLExceptionTypeDelegate.java:59)
....
...
...

Caused by: java.sql.SQLIntegrityConstraintViolationException: Duplicate entry 
'Aktenzeichen XY0000000000000003333333333444444444245555555555f' for key 'rahmenvertraege.UK_lla4el5s0eip4nwn8e3sd1trk'
    at com.mysql.cj.jdbc.exceptions.SQLError.createSQLException(SQLError.java:117) ~[mysql-connector-java-8.0.21.jar:8.0.21]
    at com.mysql.cj.jdbc.exceptions.SQLError.createSQLException(SQLError.java:97) ~[mysql-connector-java-8.0.21.jar:8.0.21]
...
..

If I take the message of the exception and deliver it via the REST api to the client he gets the upper one "could not execute statement; SQL [n/a]; constraint [rahmenvertraege.UK_lla4el5s0eip4nwn8e3sd1trk]" which is not very inforamtiv.

The nested exception at the lowest position is the one that is interessting "Duplicate entry 'Aktenzeichen XY0000000000000003333333333444444444245555555555f' for key 'rahmenvertraege.UK_lla4el5s0eip4nwn8e3sd1trk'"

But how do I do that? Just always take the deepest nested exceptions message? Isnt that dirty? And how can the client (or the service layer of the micro service) can translate that for the client? Even if the deppest message is readable, its nothing that you want to present in the user interface. There you want to see something like "Name is already used. Please choose another one". Do I have to know all types of possible exceptions an map them manually?

Thanks for your ideas.

MarkusJackson
  • 225
  • 2
  • 12

2 Answers2

1

TL;DR

The Java/Hibernate/Spring combination fails to provide useful exception objects. At application level, you're facing a very hard time to present a meaningful error description to the user.

Just from the exception chain itself, it's close to impossible to derive something meaningfull like "Name is already used. Please choose another one".

There are so many layers wrapping exceptions into other exceptions that you'd need an AI to find "the problem description".

Even the most specific exception in your case, the java.sql.SQLIntegrityConstraintViolationException just contains a text reason, not suitable for an end user, and nothing you can rely on for extracting the field that caused the violation. The only hint I can see is the repetition of the value that caused the problem.

But I'd recommend against parsing the text for such significant values or field names. Even if it works with your current setting, with the next release, your database uses a different reason text template, and you're lost.

If you don't want to write lots of exception-analyzing code, you can translate anticipated exception types to generic descriptions like

  • java.sql.SQLIntegrityConstraintViolationException: "Database integrity check: Probably some of the values you entered are already in use in the database".

This can be done generically at the top level of your REST service.

If that isn't good enough, I only see the possibility to implement exception analysis all over your code:

  • catch exceptions at places where you know what the SQL statement means,
  • do some queries, based on anticipated failure possibilities, to find out the likely cause, e.g. query for existence of the name in the database,
  • wrap the original exception into a more meaningful one, e.g. one containing failure mode ALREADY_USED, field name "name" and field value "Aktenzeichen XY0000000000000003333333333444444444245555555555f".
Ralf Kleberhoff
  • 6,990
  • 1
  • 13
  • 7
1
  • At first, @NotNull and similar constraints should be validated at controller level. Put strong validation and sanitization before it reaches other layers of application. In case of error, return HTTP status 400 Bad Request and may add meaningful message what went wrong.
  • Check if an entry exists before saving it to a database. For existing entry, return HTTP status 409 Conflict and may form meaningful message.
  • Other errors like SQLIntegrityConstraintViolationException should lead to HTTP status 500 Internal Server Error with obscure message 'Internal server error' without exposing database/server/underlying exception details to a client.

Check Error Handling for REST with Spring for examples.

Justinas Jakavonis
  • 8,220
  • 10
  • 69
  • 114