If you're working on a Spring Security (and especially an OAuth) implementation, definitely have a look at the Learn Spring Security course:
>> LEARN SPRING SECURITYMocking is an essential part of unit testing, and the Mockito library makes it easy to write clean and intuitive unit tests for your Java code.
Get started with mocking and improve your application tests using our Mockito guide:
Handling concurrency in an application can be a tricky process with many potential pitfalls. A solid grasp of the fundamentals will go a long way to help minimize these issues.
Get started with understanding multi-threaded applications with our Java Concurrency guide:
Spring 5 added support for reactive programming with the Spring WebFlux module, which has been improved upon ever since. Get started with the Reactor project basics and reactive programming in Spring Boot:
Since its introduction in Java 8, the Stream API has become a staple of Java development. The basic operations like iterating, filtering, mapping sequences of elements are deceptively simple to use.
But these can also be overused and fall into some common pitfalls.
To get a better understanding on how Streams work and how to combine them with other language features, check out our guide to Java Streams:
Get started with Spring and Spring Boot, through the Learn Spring course:
>> LEARN SPRINGExplore Spring Boot 3 and Spring 6 in-depth through building a full REST API with the framework:
Yes, Spring Security can be complex, from the more advanced functionality within the Core to the deep OAuth support in the framework.
I built the security material as two full courses - Core and OAuth, to get practical with these more complex scenarios. We explore when and how to use each feature and code through it on the backing project.
You can explore the course here:
Spring Data JPA is a great way to handle the complexity of JPA with the powerful simplicity of Spring Boot.
Get started with Spring Data JPA through the guided reference course:
Refactor Java code safely β and automatically β with OpenRewrite.
Refactoring big codebases by hand is slow, risky, and easy to put off. Thatβs where OpenRewrite comes in. The open-source framework for large-scale, automated code transformations helps teams modernize safely and consistently.
Each month, the creators and maintainers of OpenRewrite at Moderne run live, hands-on training sessions β one for newcomers and one for experienced users. Youβll see how recipes work, how to apply them across projects, and how to modernize code with confidence.
Join the next session, bring your questions, and learn how to automate the kind of work that usually eats your sprint time.
β’ The Registration Process With Spring Security
β’ Registration β Activate a New Account by Email
β’ Spring Security Registration β Resend Verification Email
β’ Registration with Spring Security β Password Encoding
β’ Registration β Password Strength and Rules
β’ Updating Your Password
β’ Notify User of Login From New Device or Location
1. Overview
In the last few articles of the Registration series here on Baeldung, we built most of the functionality we needed in an MVC fashion.
Weβre now going to transition some of these APIs to a more RESTful approach.
2. The Register Operation
Letβs start with the main Register operation:
@PostMapping("/user/registration")
public GenericResponse registerUserAccount(
@Valid UserDto accountDto, HttpServletRequest request) {
logger.debug("Registering user account with information: {}", accountDto);
User registered = createUserAccount(accountDto);
if (registered == null) {
throw new UserAlreadyExistException();
}
String appUrl = "http://" + request.getServerName() + ":" +
request.getServerPort() + request.getContextPath();
eventPublisher.publishEvent(
new OnRegistrationCompleteEvent(registered, request.getLocale(), appUrl));
return new GenericResponse("success");
}
So, how is this different from the original MVC implementation?
Here goes:
- the request is now correctly mapped to an HTTP POST
- weβre now returning a proper DTO and marshaling that directly into the body of the response
- weβre no longer dealing with error handling in the method at all
Weβre also removing the old showRegistrationPage() β as thatβs not needed to simply display the registration page.
3. The registration.html
Furthermore, we need to modify the registration.html to:
- use Ajax to submit the registration form
- receive the results of the operation as JSON
Here goes:
<html>
<head>
<title th:text="#{label.form.title}">form</title>
</head>
<body>
<form method="POST" enctype="utf8">
<input name="firstName" value="" />
<span id="firstNameError" style="display:none"></span>
<input name="lastName" value="" />
<span id="lastNameError" style="display:none"></span>
<input name="email" value="" />
<span id="emailError" style="display:none"></span>
<input name="password" value="" type="password" />
<span id="passwordError" style="display:none"></span>
<input name="matchingPassword" value="" type="password" />
<span id="globalError" style="display:none"></span>
<a href="#" onclick="register()" th:text="#{label.form.submit}>submit</a>
</form>
<script src="jquery.min.js"></script>
<script type="text/javascript">
var serverContext = [[@{/}]];
function register(){
$(".alert").html("").hide();
var formData= $('form').serialize();
$.post(serverContext + "/user/registration",formData ,function(data){
if(data.message == "success"){
window.location.href = serverContext +"/successRegister.html";
}
})
.fail(function(data) {
if(data.responseJSON.error.indexOf("MailError") > -1)
{
window.location.href = serverContext + "/emailError.html";
}
else if(data.responseJSON.error.indexOf("InternalError") > -1){
window.location.href = serverContext +
"/login.html?message=" + data.responseJSON.message;
}
else if(data.responseJSON.error == "UserAlreadyExist"){
$("#emailError").show().html(data.responseJSON.message);
}
else{
var errors = $.parseJSON(data.responseJSON.message);
$.each( errors, function( index,item ){
$("#"+item.field+"Error").show().html(item.defaultMessage);
});
errors = $.parseJSON(data.responseJSON.error);
$.each( errors, function( index,item ){
$("#globalError").show().append(item.defaultMessage+"<br>");
});
}
}
</script>
</body>
</html>
4. Exception Handling
Generally, implementing a good exception-handling strategy can make the REST API more robust and error-prone.
Weβre using the same @ControllerAdvice mechanism to deal cleanly with different exceptions β and now we need a new type of exception.
This is the BindException β which is thrown when the UserDto is validated (if invalid). Weβll override the default ResponseEntityExceptionHandler method handleBindException() to add the errors in the response body:
@Override
protected ResponseEntity<Object> handleBindException
(BindException ex, HttpHeaders headers, HttpStatus status, WebRequest request) {
logger.error("400 Status Code", ex);
BindingResult result = ex.getBindingResult();
GenericResponse bodyOfResponse =
new GenericResponse(result.getFieldErrors(), result.getGlobalErrors());
return handleExceptionInternal(
ex, bodyOfResponse, new HttpHeaders(), HttpStatus.BAD_REQUEST, request);
}
Next, we will also need to handle our custom Exception UserAlreadyExistException β which is thrown when the user registers with an email that already exists:
@ExceptionHandler({ UserAlreadyExistException.class })
public ResponseEntity<Object> handleUserAlreadyExist(RuntimeException ex, WebRequest request) {
logger.error("409 Status Code", ex);
GenericResponse bodyOfResponse = new GenericResponse(
messages.getMessage("message.regError", null, request.getLocale()), "UserAlreadyExist");
return handleExceptionInternal(
ex, bodyOfResponse, new HttpHeaders(), HttpStatus.CONFLICT, request);
}
5. The GenericResponse
We also need to improve the GenericResponse implementation to hold these validation errors:
public class GenericResponse {
public GenericResponse(List<FieldError> fieldErrors, List<ObjectError> globalErrors) {
super();
ObjectMapper mapper = new ObjectMapper();
try {
this.message = mapper.writeValueAsString(fieldErrors);
this.error = mapper.writeValueAsString(globalErrors);
} catch (JsonProcessingException e) {
this.message = "";
this.error = "";
}
}
}
6. UI β Field and Global Errors
Finally, letβs see how to handle both field and global errors using jQuery:
var serverContext = [[@{/}]];
function register(){
$(".alert").html("").hide();
var formData= $('form').serialize();
$.post(serverContext + "/user/registration",formData ,function(data){
if(data.message == "success"){
window.location.href = serverContext +"/successRegister.html";
}
})
.fail(function(data) {
if(data.responseJSON.error.indexOf("MailError") > -1)
{
window.location.href = serverContext + "/emailError.html";
}
else if(data.responseJSON.error.indexOf("InternalError") > -1){
window.location.href = serverContext +
"/login.html?message=" + data.responseJSON.message;
}
else if(data.responseJSON.error == "UserAlreadyExist"){
$("#emailError").show().html(data.responseJSON.message);
}
else{
var errors = $.parseJSON(data.responseJSON.message);
$.each( errors, function( index,item ){
$("#"+item.field+"Error").show().html(item.defaultMessage);
});
errors = $.parseJSON(data.responseJSON.error);
$.each( errors, function( index,item ){
$("#globalError").show().append(item.defaultMessage+"<br>");
});
}
}
Note that:
- If there are validation errors, then the message object contains the field errors and the error object contains global errors
- We display each field error next to its field
- We display all the global errors in one place at the end of the form
7. Conclusion
The focus of this quick article is to bring the API into a more RESTful direction and show a simple way of dealing with that API in the front end.
The jQuery front end itself is not the focus β just a basic potential client that can be implemented in any number of JS frameworks, while the API remains exactly the same.
