skip to Main Content

I have a Spring Boot controller that consumes JSON:

    @PostMapping(
    value = "/shipdetails")
    public ResponseEntity acceptShip(@RequestBody Ship ship, HttpServletRequest request) {
        shipService.checkShip(ship);
        return new ResponseEntity<>(HttpStatus.OK);
    }

There is a corresponding Ship Entity:

public class Ship implements Serializable {

@JsonProperty("Name")
private String name;
@JsonProperty("Owner")
private String owner;

public Ship(String name, String owner) {
    this.name = name;
    this.owner = owner;    }

public Ship() {
}

// Getters and Setters removed for brevity

And finally the service:

@Service
public class ShipService {

Boolean checkShip(Ship ship) {
    if (ship.getName().equals("Queen Mary")) {
         // do something
    }
//edited for brevity

Sample invalid JSON:

{
    "name_wrong":"test name",
    "owner":"Lloyds Shipping"
}

Currently the error I get on the stacktrace if I send invalid JSON is (service layer) : Cannot invoke "String.equals(Object)" because the return value of "com.ships.Ship.getName()" is null.

Jackson needs the no-args constructor for de-serialization.

When inspecting in the debugger the Ship entity has a owner set, but not a name set, so its not the the whole entity is null – just one field.

I tried my Ship entity without a default no args constructor so you could not even pass entity with a null field to the service but then it fails with even valid JSON.

How and where should the exception for invalid JSON be handled?

2

Answers


  1. Rather than viewing the issue as being a misnamed field, think of it as instead being two issues: a missing property (name) and an extra property (name_wrong).

    Spring Boot is configured by default to ignore any extra properties. And unless you tell the controller that the name property is required (e.g. by using the @NotNull bean validation annotation on the name field), it will happily accept a null value for this property.

    Missing required property

    The NullPointerException is happening since you’re attempting to call equals on the null name field.

    ship.getName().equals("Queen Mary")
    ^^^^^^^^^^^^^^
    

    There are a few different fixes for this. First, you could add a bean validation to the required fields:

    public class Ship implements Serializable {
      @NotNull
      @JsonProperty("Name")
      private String name;
    
      @JsonProperty("Owner")
      private String owner;
    
      // ...
    }
    

    Alternatively, you could change the equals method to be null-safe:

    Boolean checkShip(Ship ship) {
        if (Objects.equals(ship.getName(), "Queen Mary"))) {
             // do something
        }
    }
    

    Extra property

    There are several ways to have Spring reject the extra name_wrong property.

    If you want the application to always reject unknown properties (unless a particular bean opts out of it), you can configure it globally:

    application.yml

    spring.jackson.deserialization.fail-on-unknown-properties: false
    
    Login or Signup to reply.
  2. Need to add few annotations and checks for the validation of the request body json.

    1. Add dependency in pom.xml for the below annotations-

    <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-validation</artifactId>
                <version>3.1.1</version>
    </dependency>
    

    2. @Valid with Request Body

        @PostMapping(value = "/shipdetails")
        public ResponseEntity acceptShip(@Valid @RequestBody Ship ship, 
                                         HttpServletRequest request)
    

    3. @NotNull with the mandatory attributes

        public class Ship implements Serializable {
    
          @NotNull(message = "The name is mandatory")
          @JsonProperty("Name")
          private String name;
          @JsonProperty("Owner")
          private String owner;
    

    4. Additional null check before equals method

    Boolean checkShip(Ship ship) {
            if (StringUtils.isNotBlank(ship.getName()) 
                && ship.getName().equals("Queen Mary")) {
                // do something
            }
    

    Output with Invalid json:

    Input

        {
         "name_wrong":"test name",
         "owner":"Lloyds Shipping"
        }
    

    Output:
    You will get a 400 Bad request status with the long error stack trace includes something like this

        "message": "Validation failed for object='ship'. Error count: 1",
    "errors": [
        {
            "codes": [
                "NotNull.ship.name",
                "NotNull.name",
                "NotNull.java.lang.String",
                "NotNull"
            ],
            "arguments": [
                {
                    "codes": [
                        "ship.name",
                        "name"
                    ],
                    "arguments": null,
                    "defaultMessage": "name",
                    "code": "name"
                }
            ],
            "defaultMessage": "The name is mandatory",
            "objectName": "ship",
            "field": "name",
            "rejectedValue": null,
            "bindingFailure": false,
            "code": "NotNull"
        }
    ]
    
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search