Here is the fragment of my model's class, Student
:
@Table
public class Student {
@Id
private Long id;
private String name;
private String surname;
private Integer age;
public Student(String name, String surname, Integer age) {
validateInput(name, surname, age);
this.name = name;
this.surname = surname;
this.age = age;
}
private void validateInput(String name, String surname, Integer age) {
if (name == null || name.isBlank()) throw new InvalidNewStudentNameException(name);
if (surname == null || surname.isBlank()) throw new InvalidNewStudentSurnameException(surname);
if (age == null || age < 16) throw new InvalidNewStudentAgeException(age);
}
}
So, in case of an attempt to create an object with invalid data, corresponding custom exception is thrown, and the object is not created.
One such custom class, InvalidNewStudentAgeException
, goes as follows:
public class InvalidNewStudentAgeException extends RuntimeException {
public InvalidNewStudentAgeException(Integer age) {
super("Could not create a student: student's age, " + age + ", should not be empty or less than 16.");
}
}
My endpoint for adding new student in StudentController
:
@GetMapping("/add")
@ResponseBody
public Mono<Long> add(@RequestParam String name, @RequestParam String surname, @RequestParam Integer age) {
return studentService.add(name, surname, age).map(student -> student.getId());
}
And corresponding method in StudentService
:
@Override
public Mono<Student> add(String name, String surname, Integer age) {
return client.execute("INSERT INTO student (age, name, surname) VALUES(:age, :name, :surname)")
.bind("age", age)
.bind("name", name)
.bind("surname", surname)
.as(Student.class).fetch().one();
}
I run the app and try to add invalid student (the one with age less than 16) via:
http://localhost:8080/add/?name=SomeName&surname=SomeSurname&age=14
I do indeed get my custom exception thrown, however, the message is only visible within my IDE in stack trace, but not in browser. The app terminates, and if I then access http://localhost:8080/, I get either Whitelabel Error Page with Internal Server Error (500), or just the error without the page if I add server.error.whitelabel.enabled=false
to my application.properties
I tried various ways to handle this exception so that I'll get my custom error message for the user (browser), but no success so far.
Please help me with a proper way to catch my custom exceptions in the controller and handle it in a way that it'll display a message about invalid data in client's browser.
Even the dirty and obviously WRONG workaround of disabling whitelabel pages and adding error.html file in src\main\resources\templates
project directory doesn't work for some reason...
Here is one way I tried to do this. I changed my StudentController
's add()
method like this:
@GetMapping("/add")
@ResponseBody
public Mono<Long> add(@RequestParam String name, @RequestParam String surname, @RequestParam Integer age) {
try {
return studentService.add(name, surname, age).map(student -> student.getId());
}
catch (InvalidNewStudentAgeException invNewStudAgeEx) {
return Mono.error(invNewStudAgeEx);
}
catch (InvalidNewStudentNameException invNewStudNameEx) {
return Mono.error(invNewStudNameEx);
}
catch (InvalidNewStudentSurnameException invNewStudSurnameEx) {
return Mono.error(invNewStudSurnameEx);
}
}
I've added handlers like this:
@ResponseStatus(
value = HttpStatus.BAD_REQUEST,
reason = "Invalid student's age: age should not be empty or less than 16.")
@ExceptionHandler(InvalidNewStudentAgeException.class)
public void invalidNewStudentAgeHandler() {
}
@ResponseStatus(
value = HttpStatus.BAD_REQUEST,
reason = "Invalid student's name: the name should not be empty.")
@ExceptionHandler(InvalidNewStudentNameException.class)
public void invalidNewStudentNameHandler() {
}
@ResponseStatus(
value = HttpStatus.BAD_REQUEST,
reason = "Invalid student's surname: the surname should not be empty.")
@ExceptionHandler(InvalidNewStudentSurnameException.class)
public void invalidNewStudentSurnameHandler() {
}
I figured out that if I change HttpStatus
's ENUM for invalid age handler and then try to add under-aged student, it does return different response code depending on the ENUM. So it does catch the exception in the handler, but how can I obtain MORE than just a number like 500 or 404? Can I somehow make the browser to display "Invadin student's age..." instead of just HTTP ERROR 509
???
client.execute
feels like a blocking call. It should be returning aMono.error
and not throw an exception if something goes wrong. What database driver are you using. – ToerktumlareMono.error
there. I use r2dbc and in-memory H2 database:spring.r2dbc.url=r2dbc:h2:mem:///studentlist;DB_CLOSE_DELAY=-1
– Michael