Lab 8: Exception Messages
One benefit of server-side rendering is leveraging server exceptions to display useful information to the user.
In this lab, we will catch Exceptions using a Spring @ControllerAdvice
and show them to the user in a toast message.
Exceptions
We start by creating an InfoException base class in the de.tschuehly.easy.spring.auth.web.exception
package:
package de.tschuehly.easy.spring.auth.web.exception;
public class InfoException extends RuntimeException{
public InfoException(String message) {
super(message);
}
}
Then we create a UserNotFoundException
that extends the InfoException in the de.tschuehly.easy.spring.auth.user
package:
package de.tschuehly.easy.spring.auth.user;
import de.tschuehly.easy.spring.auth.web.exception.InfoException;
public class UserNotFoundException extends InfoException {
public UserNotFoundException(String message) {
super(message);
}
}
UserService
Now we will adjust the UserService.searchUser
method to throw the UserNotFoundException
when no users are found.
public List<EasyUser> searchUser(String searchString) {
List<EasyUser> easyUsers = easyUserList.stream().filter(
it -> it.uuid.toString().contains(searchString)
|| it.username.contains(searchString)
|| it.password.contains(
searchString)
).toList();
if (easyUsers.isEmpty()) {
throw new UserNotFoundException("No user found with the searchString: \"" + searchString + "\"");
}
return easyUsers;
}
LayoutComponent
Now we need to create a container element for the message in the LayoutComponent
.
We first define a constant TOAST_CONTAINER_ID
:
public static final String TOAST_CONTAINER_ID = "toastContainer";
We first import the TOAST_CONTAINER_ID
in the LayoutComponent.jte
@import static de.tschuehly.easy.spring.auth.web.layout.LayoutComponent.TOAST_CONTAINER_ID
Then we add a <div>
element that has the ID set to TOAST_CONTAINER_ID
after the <body>
element.
<div id="${TOAST_CONTAINER_ID}">
</div>
MessageComponent
Now we create a MessageComponent
ViewComponent in the de.tschuehly.easy.spring.auth.web.message
package.
We also define an MessageType
enum that has a severity
method.
package de.tschuehly.easy.spring.auth.web.message;
import de.tschuehly.spring.viewcomponent.core.component.ViewComponent;
import de.tschuehly.spring.viewcomponent.jte.ViewContext;
@ViewComponent
public class MessageComponent {
public ViewContext renderInfoToast(String message) {
return new MessageContext(message, MessageType.INFO);
}
public record MessageContext(String message, MessageType type) implements
ViewContext {}
public enum MessageType {
TOAST_ERROR,
NONE,
INFO;
public String severity() {
return switch (this) {
case TOAST_ERROR -> "error";
case INFO -> "info";
default -> "";
};
}
}
}
Now we will create the corresponding MessageComponent.jte
@import de.tschuehly.easy.spring.auth.web.message.MessageComponent.MessageContext
@import java.util.Date
@param MessageContext messageContext
!{var id = String.valueOf(new Date().getTime());}
<div role="alert" id="${id}" <%-- (1) --%>
style="position: fixed; margin: 2rem; top: 0; left: 0; border-radius: 1rem; background-color: antiquewhite; padding: 1rem;">
<button onclick="document.getElementById('${id}').style.display = 'none'"> <%-- (2) --%>
<i class="">X</i>
</button>
<div>
<h2>${messageContext.type().severity()}:
${messageContext.message()}</h2> <%-- (3) --%>
</div>
</div>
(1): In the template, we create an <div>
element where we set the id
to a timestamp, we define as a local variable with the !{var}
syntax
(2): We use the <button onclick>
attribute to hide the toast when the user clicks on the X
(3): We create a <h2>
element in it that shows the severity and the message of the exception.
ControllerAdvice
Now we will create an ExceptionAdvice.java
class in the de.tschuehly.easy.spring.auth.web.advice
package:
package de.tschuehly.easy.spring.auth.web.advice;
import de.tschuehly.easy.spring.auth.htmx.HtmxUtil;
import de.tschuehly.easy.spring.auth.web.exception.InfoException;
import de.tschuehly.easy.spring.auth.web.layout.LayoutComponent;
import de.tschuehly.easy.spring.auth.web.message.MessageComponent;
import de.tschuehly.spring.viewcomponent.jte.ViewContext;
import io.github.wimdeblauwe.htmx.spring.boot.mvc.HxSwapType;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
@ControllerAdvice // (1)
public class ExceptionAdvice {
private final MessageComponent messageComponent;
public ExceptionAdvice(MessageComponent messageComponent) { // (2)
this.messageComponent = messageComponent;
}
@ExceptionHandler(InfoException.class) // (3)
public ViewContext handle(InfoException e) {
HtmxUtil.retarget(HtmxUtil.idSelector(LayoutComponent.TOAST_CONTAINER_ID)); // (4)
HtmxUtil.swap(HxSwapType.INNER_HTML); // (5)
return messageComponent.renderInfoToast(e.getMessage()); // (6)
}
}
(1): We annotate the class with the@ControllerAdvice
annotation.
(2): We autowire the MessageComponent
(3): We define an @ExceptionHandler
method that handles the InfoException.class
(4): We retarget the response to the <div>
we created earlier with the TOAST_CONTAINER_ID
(5): We set the Swap method to INNER_HTML
Sucess!
Restart the application and navigate to http://localhost:8080.
Now search for a string not present in the data, and then you will see the toast message with the exception message.

Lab-8 Checkpoint 1
If you are stuck you can resume at this checkpoint with:
git checkout tags/lab-8-checkpoint-1 -b lab-8-c1
Last updated