Young Choi
Young Choi

Categories

When building a REST API with Java Spring Framework, handling multiple types of requests efficiently can be challenging. One effective approach is to use interfaces for controller requests. In this blog post, we’ll walk through an example using a restaurant order system. We’ll handle different types of orders (like hamburger and pizza) with a single endpoint.

Scenario: Restaurant Order System

Imagine we have an endpoint /v1/orders that accepts different types of orders. Each order type has some common fields, but also unique fields. We’ll use an interface called Order and implement it with specific classes for each order type. Let’s dive in.

Step 1: Define the Order Interface

First, we create an Order interface with common fields that all orders will have.

public interface Order {
    String getOrderType();
}

Step 2: Implement Order Types

Next, we create classes for specific order types, like HamburgerOrder and PizzaOrder.

import lombok.Data;
import javax.validation.constraints.NotNull;

@Data
public class HamburgerOrder implements Order {
    @NotNull
    private String orderType = "hamburger";
    @NotNull
    private String bunType;
    @NotNull
    private String meatType;
    private boolean extraCheese;
}
import lombok.Data;
import javax.validation.constraints.NotNull;

@Data
public class PizzaOrder implements Order {
    @NotNull
    private String orderType = "pizza";
    @NotNull
    private String crustType;
    @NotNull
    private String size;
    private boolean extraToppings;
}

Step 3: Create the Controller

Now, we create the controller to handle the /v1/orders POST request. We’ll use @RequestBody and @Valid annotations to ensure the request body is correctly validated.

import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;

import javax.validation.Valid;

@RestController
@RequestMapping("/v1/orders")
@Validated
public class OrderController {

    @PostMapping
    public ResponseEntity<String> createOrder(@Valid @RequestBody Order order) {
        if (order instanceof HamburgerOrder) {
            HamburgerOrder hamburgerOrder = (HamburgerOrder) order;
            // Process hamburger order
            return new ResponseEntity<>("Hamburger order received", HttpStatus.OK);
        } else if (order instanceof PizzaOrder) {
            PizzaOrder pizzaOrder = (PizzaOrder) order;
            // Process pizza order
            return new ResponseEntity<>("Pizza order received", HttpStatus.OK);
        } else {
            return new ResponseEntity<>("Unknown order type", HttpStatus.BAD_REQUEST);
        }
    }
}

Step 4: Configure Jackson for Polymorphic Deserialization

To handle polymorphic deserialization, we need to configure Jackson. This allows us to deserialize JSON into the correct Order implementation.

import com.fasterxml.jackson.annotation.JsonSubTypes;
import com.fasterxml.jackson.annotation.JsonTypeInfo;

@JsonTypeInfo(
        use = JsonTypeInfo.Id.NAME,
        include = JsonTypeInfo.As.PROPERTY,
        property = "orderType"
)
@JsonSubTypes({
        @JsonSubTypes.Type(value = HamburgerOrder.class, name = "hamburger"),
        @JsonSubTypes.Type(value = PizzaOrder.class, name = "pizza")
})
public interface Order {
    String getOrderType();
}

Step 5: Add Global Validation Handling

Finally, add global exception handling to manage validation errors.

import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;

@ControllerAdvice
public class GlobalExceptionHandler {

    @ExceptionHandler(MethodArgumentNotValidException.class)
    public ResponseEntity<String> handleValidationExceptions(MethodArgumentNotValidException ex) {
        return new ResponseEntity<>("Invalid order data", HttpStatus.BAD_REQUEST);
    }
}

Conclusion

Using interfaces for controller requests in Java Spring Framework allows you to handle multiple types of requests cleanly and efficiently. In this example, we created an Order interface and implemented specific order types, handled polymorphic deserialization with Jackson, and ensured validation with @Valid and @RequestBody. This approach makes your code more flexible and easier to maintain. Happy coding!