1. Introduction

In this tutorial, we’ll learn how to validate the Boolean type in a Spring Boot application and look at various ways to perform the validation. Furthermore, we’ll validate an object of type Boolean at various Spring Boot application layers, such as the controller or service layer.

2. Programmatic Validation

The Boolean class provides two essential methods to create an instance of the class: Boolean.valueOf() and Boolean.parseBoolean().

Boolean.valueOf() accepts both String and boolean values. It checks if the value of the input field is true or false and accordingly provides a Boolean object. The Boolean.parseBoolean() method only accepts string values.

These methods are not case-sensitive — for example, “true”, “True”, “TRUE”, “false”, “False”, and “FALSE” are acceptable inputs.

Let’s validate the String to Boolean conversions with a unit test:

@Test
void givenInputAsString_whenStringToBoolean_thenValidBooleanConversion() {
    assertEquals(Boolean.TRUE, Boolean.valueOf("TRUE"));
    assertEquals(Boolean.FALSE, Boolean.valueOf("false"));
    assertEquals(Boolean.TRUE, Boolean.parseBoolean("True"));
}

We’ll now validate the conversion from the primitive boolean value to the Boolean wrapper class:

@Test
void givenInputAsboolean_whenbooleanToBoolean_thenValidBooleanConversion() {
    assertEquals(Boolean.TRUE, Boolean.valueOf(true));
    assertEquals(Boolean.FALSE, Boolean.valueOf(false));
}

3. Validation With Custom Jackson Deserializer

As Spring Boot APIs often handle JSON data, we’ll also look at how we can validate the JSON to Boolean conversion via data deserialization. We can deserialize custom representations of boolean values using a custom deserializer.

Let’s consider a scenario where we want to consume JSON data that represents a boolean value with + (for true) and (for false). Let’s write a JSON deserializer to achieve this:

public class BooleanDeserializer extends JsonDeserializer<Boolean> {
    @Override
    public Boolean deserialize(JsonParser parser, DeserializationContext context) throws IOException {
        String value = parser.getText();
        if (value != null && value.equals("+")) {
            return Boolean.TRUE;
        } else if (value != null && value.equals("-")) {
            return Boolean.FALSE;
        } else {
            throw new IllegalArgumentException("Only values accepted as Boolean are + and -");
        }
    }
}

4. Bean Validation With Annotations

Bean validation constraints are another popular approach to validate fields. To use this, we need spring-boot-starter-validation dependency. Out of all available validation annotations, three of them can be used for a Boolean field:

  • @NotNull: Produces an error if the Boolean field is null
  • @AssertTrue: Produces an error if the Boolean field is set to false
  • @AssertFalse: Produces an error if the Boolean field is set to true

It’s important to note that both @AssertTrue and @AssertFalse consider null values as valid input. That means, if we want to make sure that only actual boolean values are accepted, we need to use these two annotations in combination with @NotNull.

5. Example of Boolean Validation

To demonstrate this, we’ll use both bean constraints and a custom JSON deserializer at the controller and service layers. Let’s create a custom object named BooleanObject with four parameters of Boolean type. Each of them will use a different validation approach:

public class BooleanObject {

    @NotNull(message = "boolField cannot be null")
    Boolean boolField;

    @AssertTrue(message = "trueField must have true value")
    Boolean trueField;

    @NotNull(message = "falseField cannot be null")
    @AssertFalse(message = "falseField must have false value")
    Boolean falseField;

    @JsonDeserialize(using = BooleanDeserializer.class)
    Boolean boolStringVar;

    //getters and setters
}

6. Validation in the Controller

Whenever we pass an object via a RequestBody to a REST endpoint, we can use the @Valid annotation to validate the object. When we apply the @Valid annotation to a method parameter, we instruct Spring to validate the respective user object:

@RestController
public class ValidationController {

    @Autowired
    ValidationService service;

    @PostMapping("/validateBoolean")
    public ResponseEntity<String> processBooleanObject(@RequestBody @Valid BooleanObject booleanObj) {
        return ResponseEntity.ok("BooleanObject is valid");
    }

    @PostMapping("/validateBooleanAtService")
    public ResponseEntity<String> processBooleanObjectAtService() {
        BooleanObject boolObj = new BooleanObject();
        boolObj.setBoolField(Boolean.TRUE);
        boolObj.setTrueField(Boolean.FALSE);
        service.processBoolean(boolObj);
        return ResponseEntity.ok("BooleanObject is valid");
    }
}

After the validation, if any violation is found, Spring will throw MethodArgumentNotValidException. To handle this, ControllerAdvice with the relevant ExceptionHandler methods can be used. Let’s create three methods to handle respective exceptions thrown from the Controller and Service layer:

@RestControllerAdvice
public class GlobalExceptionHandler {

    @ExceptionHandler(MethodArgumentNotValidException.class)
    @ResponseStatus(value = HttpStatus.BAD_REQUEST)
    public String handleValidationException(MethodArgumentNotValidException ex) {
        return ex.getBindingResult()
            .getFieldErrors()
            .stream()
            .map(e -> e.getDefaultMessage())
            .collect(Collectors.joining(","));
    }

    @ExceptionHandler(IllegalArgumentException.class)
    @ResponseStatus(value = HttpStatus.BAD_REQUEST)
    public String handleIllegalArugmentException(IllegalArgumentException ex) {
        return ex.getMessage();
    }

    @ExceptionHandler(ConstraintViolationException.class)
    @ResponseStatus(value = HttpStatus.INTERNAL_SERVER_ERROR)
    public String handleConstraintViolationException(ConstraintViolationException ex) {
       return ex.getMessage();
    }
}

Before testing the REST functionality, we recommend going through testing APIs in Spring Boot. Let’s create the structure of the test class for the Controller:

@ExtendWith(SpringExtension.class)
@WebMvcTest(controllers = ValidationController.class)
class ValidationControllerUnitTest {

    @Autowired
    private MockMvc mockMvc;

    @TestConfiguration
    static class EmployeeServiceImplTestContextConfiguration {
        @Bean
        public ValidationService validationService() {
            return new ValidationService() {};
        }
    }

    @Autowired
    ValidationService service;
}

With this in place, we can now test the validation annotations we’ve used in our class.

6.1. Validate @NotNull Annotation

Let’s look at how the @NotNull works. When we pass the BooleanObject with a null Boolean parameter, @Valid annotation will validate the bean and throw a “400 Bad Request” HTTP response:

@Test
void whenNullInputForBooleanField_thenHttpBadRequestAsHttpResponse() throws Exception {
    String postBody = "{\"boolField\":null,\"trueField\":true,\"falseField\":false,\"boolStringVar\":\"+\"}";

    mockMvc.perform(post("/validateBoolean").contentType("application/json")
            .content(postBody))
        .andExpect(status().isBadRequest());
}

6.2. Validate @AssertTrue Annotation

Next, we’ll test the working of @AssertTrue. When we pass the BooleanObject with a false Boolean parameter, @Valid annotation will validate the bean and throw a “400 Bad Request” HTTP response. If we capture the response body, we can get the error message set inside the @AssertTrue annotation:

 @Test
 void whenInvalidInputForTrueBooleanField_thenErrorResponse() throws Exception {
     String postBody = "{\"boolField\":true,\"trueField\":false,\"falseField\":false,\"boolStringVar\":\"+\"}";

     String output = mockMvc.perform(post("/validateBoolean").contentType("application/json")
             .content(postBody))
         .andReturn()
         .getResponse()
         .getContentAsString();

     assertEquals("trueField must have true value", output);
 }

Let’s also check what happens if we provide null. Since we annotated the field only with @AssertTrue, but not with @NotNull, there’s no validation error:

 @Test
 void whenNullInputForTrueBooleanField_thenCorrectResponse() throws Exception {
    String postBody = "{\"boolField\":true,\"trueField\":null,\"falseField\":false,\"boolStringVar\":\"+\"}";

    mockMvc.perform(post("/validateBoolean").contentType("application/json")
                    .content(postBody))
            .andExpect(status().isOk());
}

6.3. Validate @AssertFalse Annotation

We’ll now understand the working of @AssertFalse. When we pass a true value for the @AssertFalse parameter, the @Valid annotation throws a bad request. We can get the error message set against the @AssertFalse annotation in the response body:

 @Test
 void whenInvalidInputForFalseBooleanField_thenErrorResponse() throws Exception {
     String postBody = "{\"boolField\":true,\"trueField\":true,\"falseField\":true,\"boolStringVar\":\"+\"}";

     String output = mockMvc.perform(post("/validateBoolean").contentType("application/json")
             .content(postBody))
         .andReturn()
         .getResponse()
         .getContentAsString();

     assertEquals("falseField must have false value", output);
 }

Again, let’s see what happens if we provide null. We annotated the field with both @AssertFalse and @NotNull, therefore, we’ll get a validation error:

 @Test
 void whenNullInputForFalseBooleanField_thenHttpBadRequestAsHttpResponse() throws Exception {
    String postBody = "{\"boolField\":true,\"trueField\":true,\"falseField\":null,\"boolStringVar\":\"+\"}";

    mockMvc.perform(post("/validateBoolean").contentType("application/json")
                    .content(postBody))
            .andExpect(status().isBadRequest());
 }

6.4. Validate Custom JSON Deserializer for Boolean Type

Let’s validate the parameter marked with our custom JSON deserializer. The custom deserializer only accepts the values “+” and “-“. If we pass any other value, the validation will fail and trigger an error. Let’s pass the “plus” text value in the input JSON and see how the validation works:

@Test
void whenInvalidBooleanFromJson_thenErrorResponse() throws Exception {
    String postBody = "{\"boolField\":true,\"trueField\":true,\"falseField\":false,\"boolStringVar\":\"plus\"}";

    String output = mockMvc.perform(post("/validateBoolean").contentType("application/json")
            .content(postBody))
        .andReturn()
        .getResponse()
        .getContentAsString();

    assertEquals("Only values accepted as Boolean are + and -", output);
}

Finally, let’s test the happy case scenario. We’ll pass the “+” sign as input for the custom deserialized field. Since it’s a valid input, the validation will pass and give a successful response:

 @Test
 void whenAllBooleanFieldsValid_thenCorrectResponse() throws Exception {
     String postBody = "{\"boolField\":true,\"trueField\":true,\"falseField\":false,\"boolStringVar\":\"+\"}";

     String output = mockMvc.perform(post("/validateBoolean").contentType("application/json")
             .content(postBody))
         .andReturn()
         .getResponse()
         .getContentAsString();

     assertEquals("BooleanObject is valid", output);
 }

7. Validation in the Service

Let’s now look at the validation at the service layer. To achieve this, we annotate the service class with @Validated annotation and place the @Valid annotation against the method parameter. The combination of these two annotations will cause Spring Boot to validate the object.

Unlike @RequestBody at the controller layer, the validation at the service layer takes place against a simple Java object, hence the framework triggers a ConstraintViolationException for validation failure. Spring framework will return HttpStatus.INTERNAL_SERVER_ERROR for the response status in such cases.

Service level validations are preferred for objects created or amended at the controller layer and then passed to the service layer for processing.

Let’s check the skeleton of this service class:

@Service
@Validated
public class ValidationService {

    public void processBoolean(@Valid BooleanObject booleanObj) {
        // further processing
    }
}

In the previous section, we created an endpoint to test the service layer and exception handler method to tackle ConstraintViolationException. Let’s write up a new test case to check this:

@Test
void givenAllBooleanFieldsValid_whenServiceValidationFails_thenErrorResponse() throws Exception {
    mockMvc.perform(post("/validateBooleanAtService").contentType("application/json"))
        .andExpect(status().isInternalServerError());
}

8. Conclusion

We learned how to validate the Boolean type at the controller and service layers with three approaches: programmatic validation, bean validation, and using a custom JSON deserializer.