Expand Authors Top

If you have a few years of experience in the Java ecosystem and you’d like to share that with the community, have a look at our Contribution Guidelines.

Expanded Audience – Frontegg – Security (partner)
announcement - icon User management is very complex, when implemented properly. No surprise here.

Not having to roll all of that out manually, but instead integrating a mature, fully-fledged solution - yeah, that makes a lot of sense.
That's basically what Frontegg is - User Management for your application. It's focused on making your app scalable, secure and enjoyable for your users.
From signup to authentication, it supports simple scenarios all the way to complex and custom application logic.

Have a look:

>> Elegant User Management, Tailor-made for B2B SaaS

November Discount Launch 2022 – Top
We’re finally running a Black Friday launch. All Courses are 30% off until end-of-day today:

>> GET ACCESS NOW

NPI – Lightrun – Spring (partner)

We rely on other people’s code in our own work. Every day. It might be the language you’re writing in, the framework you’re building on, or some esoteric piece of software that does one thing so well you never found the need to implement it yourself.

The problem is, of course, when things fall apart in production - debugging the implementation of a 3rd party library you have no intimate knowledge of is, to say the least, tricky. It’s difficult to understand what talks to what and, specifically, which part of the underlying library is at fault.

Lightrun is a new kind of debugger.

It's one geared specifically towards real-life production environments. Using Lightrun, you can drill down into running applications, including 3rd party dependencies, with real-time logs, snapshots, and metrics. No hotfixes, redeployments, or restarts required.

Learn more in this quick, 5-minute Lightrun tutorial:

>> The Essential List of Spring Boot Annotations and Their Use Cases

1. Introduction

Message interpolation is the process used for creating error messages for Java bean validation constraints. For example, we can see the messages by providing a null value for a field annotated with the javax.validation.constraints.NotNull annotation.

In this tutorial, we'll learn how to use the default Spring message interpolation and how to create our own interpolation mechanism.

To see examples of other libraries providing constraints besides javax.validation, take a look at Hibernate Validator Specific Constraints. We can also create a custom Spring Validation annotation.

2. Default Message Interpolation

Before getting into code snippets, let's consider an example of an HTTP 400 response with a default @NotNull constraint violation message:

{
    ....
    "status": 400,
    "error": "Bad Request",
    "errors": [
        {
            ....
            "defaultMessage": "must not be null",
            ....
        }
    ],
    "message": "Validation failed for object='notNullRequest'. Error count: 1",
    ....
}

Spring retrieves the constraint violation message details from message descriptors. Each constraint defines its default message descriptor using the message attribute. But, of course, we can overwrite it with a custom value.

As an example, we'll create a simple REST controller with a POST method:

@RestController
public class RestExample {

    @PostMapping("/test-not-null")
    public void testNotNull(@Valid @RequestBody NotNullRequest request) {
        // ...
    }
}

The request body will be mapped to the NotNullRequest object, which has just one String filed annotated with @NotNull:

public class NotNullRequest {

    @NotNull(message = "stringValue has to be present")
    private String stringValue;

    // getters, setters
}

Now, when we send in a POST request that fails this validation check, we will see our custom error message:

{
    ...
    "errors": [
        {
            ...
            "defaultMessage": "stringValue has to be present",
            ...
        }
    ],
    ...
}

The only value that changes is defaultMessage. But we still get a lot of information about error codes, object name, field name, etc. To limit the number of displayed values, we can implement Custom Error Message Handling for REST API.

3. Interpolation with Message Expressions

In Spring, we can use the Unified Expression Language to define our message descriptors. This allows defining error messages based on conditional logic and also enables advanced formatting options.

To understand it more clearly, let's look at a few examples.

In every constraint annotation, we can access the actual value of a field that's being validated:

@Size(
  min = 5,
  max = 14,
  message = "The author email '${validatedValue}' must be between {min} and {max} characters long"
)
private String authorEmail;

Our error message will contain both the actual value of the property and min and max parameters of the @Size annotation:

"defaultMessage": "The author email '[email protected]' must be between 5 and 14 characters long"

Notice that for accessing external variables, we use ${} syntax, but for accessing other properties from the validation annotation, we use {}.

Using the ternary operator is also possible:

@Min(
  value = 1,
  message = "There must be at least {value} test{value > 1 ? 's' : ''} in the test case"
)
private int testCount;

Spring will convert the ternary operator to a single value in the error message:

"defaultMessage": "There must be at least 2 tests in the test case"

We can also call methods on external variables:

@DecimalMin(
  value = "50",
  message = "The code coverage ${formatter.format('%1$.2f', validatedValue)} must be higher than {value}%"
)
private double codeCoverage;

Invalid input will produce an error message with the formatted value:

"defaultMessage": "The code coverage 44.44 must be higher than 50%"

As we can see from these examples, some characters such as {, }, $, and / are used in message expressions, so we need to escape them with a backslash character before using them literally: \{, \}, \$, and \\.

4. Custom Message Interpolation

In some cases, we want to implement a custom message interpolation engine. To do so, we must first implement the javax.validation.MessageInterpolation interface:

public class MyMessageInterpolator implements MessageInterpolator {
    private final MessageInterpolator defaultInterpolator;

    public MyMessageInterpolator(MessageInterpolator interpolator) {
        this.defaultInterpolator = interpolator;
    }

    @Override
    public String interpolate(String messageTemplate, Context context) {
        messageTemplate = messageTemplate.toUpperCase();
        return defaultInterpolator.interpolate(messageTemplate, context);
    }

    @Override
    public String interpolate(String messageTemplate, Context context, Locale locale) {
        messageTemplate = messageTemplate.toUpperCase();
        return defaultInterpolator.interpolate(messageTemplate, context, locale);
    }
}

In this simple implementation, we're just changing the error message to upper-case. By doing so, our error message will look like:

"defaultMessage": "THE CODE COVERAGE 44.44 MUST BE HIGHER THAN 50%"

We also need to register our interpolator in the javax.validation.Validation factory:

Validation.byDefaultProvider().configure().messageInterpolator(
  new MyMessageInterpolator(
    Validation.byDefaultProvider().configure().getDefaultMessageInterpolator())
);

5. Conclusion

In this article, we've learned how default Spring message interpolation works and how to create a custom message interpolation engine.

And, as always, all source code is available over on GitHub.

November Discount Launch 2022 – Bottom
We’re finally running a Black Friday launch. All Courses are 30% off until end-of-day today:

>> GET ACCESS NOW

Generic footer banner
Comments are closed on this article!