Java

Lesson 09

Records and DTO boundaries

Use records or explicit DTOs for request and response shapes instead of untyped maps that push errors deeper into the application.

Good Code

src/main/java/dev/review/api/CreateUserRequest.java
public record CreateUserRequest(String email, String displayName) {
    public CreateUserRequest {
        if (email == null || email.isBlank()) {
            throw new IllegalArgumentException("email is required");
        }

        if (displayName == null || displayName.isBlank()) {
            throw new IllegalArgumentException("displayName is required");
        }
    }
}

Bad Code

src/main/java/UserController.java
public UserResponse create(Map<String, Object> body) {
    String email = (String) body.get("email");
    String name = (String) body.get("name");

    User user = users.create(email.trim(), name.trim());

    return new UserResponse(user.id(), user.email(), user.name());
}

Review Notes

What to review

Good Code

The good version gives the boundary a name and validates required fields before the service receives them.

Bad Code

The bad version treats the request as a loose map, relies on casts, and uses a different key name than the domain concept. Missing values fail later in confusing ways.

Takeaways

  • Boundary data should cross the application as a named shape with validation close to the edge.