You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Java最佳实践:如何处理需追溯原因的对象未找到场景

Handling Object Not Found Scenarios in Java with Clear Error Context

Great question—returning null in cases like login failures is a classic anti-pattern because it strips away critical context about why the operation failed. Users need actionable feedback, and developers need debuggable details. Here are two industry-standard approaches to solve this problem, tailored to your login example:


Approach 1: Custom Exceptions for Failures

When the failure is an exceptional case (or you want to enforce explicit error handling), create specific exceptions that carry detailed error context. This lets you throw meaningful errors instead of returning null.

Step 1: Define a Custom Exception

Create an exception class that includes an enum for specific failure types (to make handling easier) and context-rich messages:

public class LoginFailedException extends RuntimeException {
    public enum ErrorType {
        USERNAME_EMPTY,
        PASSWORD_EMPTY,
        USER_NOT_FOUND,
        INCORRECT_PASSWORD
    }

    private final ErrorType errorType;

    public LoginFailedException(String message, ErrorType errorType) {
        super(message);
        this.errorType = errorType;
    }

    public ErrorType getErrorType() {
        return errorType;
    }
}

Step 2: Modify the Login Method to Throw Exceptions

Replace null returns with targeted exceptions for each failure scenario:

public User login(String username, String password) {
    // Check for empty username
    if (username == null || username.isEmpty()) {
        throw new LoginFailedException("Username cannot be empty", LoginFailedException.ErrorType.USERNAME_EMPTY);
    }
    // Check for empty password
    if (password == null || password.isEmpty()) {
        throw new LoginFailedException("Password cannot be empty", LoginFailedException.ErrorType.PASSWORD_EMPTY);
    }
    // Look up user in repository
    User user = userRepository.findByUsername(username);
    if (user == null) {
        throw new LoginFailedException("User with username '" + username + "' not found", LoginFailedException.ErrorType.USER_NOT_FOUND);
    }
    // Verify password match
    if (!passwordEncoder.matches(password, user.getHashedPassword())) {
        throw new LoginFailedException("Incorrect password for user '" + username + "'", LoginFailedException.ErrorType.INCORRECT_PASSWORD);
    }
    return user;
}

Step 3: Handle the Exception in Caller Code

Catch the exception and use the ErrorType to provide user-friendly feedback:

try {
    User user = authService.login(inputUsername, inputPassword);
    // Handle successful login (e.g., redirect to dashboard)
    System.out.println("Welcome back, " + user.getDisplayName());
} catch (LoginFailedException e) {
    // Display targeted error messages to the user
    switch (e.getErrorType()) {
        case USERNAME_EMPTY:
            showUserError("Please enter your username");
            break;
        case PASSWORD_EMPTY:
            showUserError("Please enter your password");
            break;
        case USER_NOT_FOUND:
            showUserError("This username is not registered");
            break;
        case INCORRECT_PASSWORD:
            showUserError("Password is incorrect");
            break;
    }
    // Log detailed error for debugging
    logger.error("Login failed: {}", e.getMessage(), e);
}

Approach 2: Result Wrapper Object for Expected Failures

If login failures are a normal part of user interaction (not truly "exceptional"), use a generic Result wrapper to return both success and failure states with context. This avoids exception overhead and makes failure handling explicit.

Step 1: Define a Result Wrapper

Create a generic class to encapsulate success/failure data and error details:

public class Result<T> {
    private final boolean success;
    private final T data;
    private final String errorMessage;
    private final ErrorCode errorCode;

    // Private constructor—use static factory methods instead
    private Result(boolean success, T data, String errorMessage, ErrorCode errorCode) {
        this.success = success;
        this.data = data;
        this.errorMessage = errorMessage;
        this.errorCode = errorCode;
    }

    // Factory method for successful results
    public static <T> Result<T> success(T data) {
        return new Result<>(true, data, null, null);
    }

    // Factory method for failed results
    public static <T> Result<T> failure(String errorMessage, ErrorCode errorCode) {
        return new Result<>(false, null, errorMessage, errorCode);
    }

    // Getters
    public boolean isSuccess() { return success; }
    public T getData() { return data; }
    public String getErrorMessage() { return errorMessage; }
    public ErrorCode getErrorCode() { return errorCode; }

    // Enum for standardized error codes
    public enum ErrorCode {
        USERNAME_EMPTY,
        PASSWORD_EMPTY,
        USER_NOT_FOUND,
        INCORRECT_PASSWORD
    }
}

Step 2: Modify the Login Method to Return Result

Return a Result<User> instead of User or null:

public Result<User> login(String username, String password) {
    if (username == null || username.isEmpty()) {
        return Result.failure("Username cannot be empty", Result.ErrorCode.USERNAME_EMPTY);
    }
    if (password == null || password.isEmpty()) {
        return Result.failure("Password cannot be empty", Result.ErrorCode.PASSWORD_EMPTY);
    }
    User user = userRepository.findByUsername(username);
    if (user == null) {
        return Result.failure("User with username '" + username + "' not found", Result.ErrorCode.USER_NOT_FOUND);
    }
    if (!passwordEncoder.matches(password, user.getHashedPassword())) {
        return Result.failure("Incorrect password for user '" + username + "'", Result.ErrorCode.INCORRECT_PASSWORD);
    }
    return Result.success(user);
}

Step 3: Handle the Result in Caller Code

Check the success state and handle accordingly:

Result<User> loginResult = authService.login(inputUsername, inputPassword);
if (loginResult.isSuccess()) {
    User user = loginResult.getData();
    System.out.println("Welcome back, " + user.getDisplayName());
} else {
    // Display user-friendly error
    switch (loginResult.getErrorCode()) {
        case USERNAME_EMPTY:
            showUserError("Please enter your username");
            break;
        case PASSWORD_EMPTY:
            showUserError("Please enter your password");
            break;
        case USER_NOT_FOUND:
            showUserError("This username is not registered");
            break;
        case INCORRECT_PASSWORD:
            showUserError("Password is incorrect");
            break;
    }
    logger.error("Login failed: {}", loginResult.getErrorMessage());
}

Key Best Practices to Follow

  • Choose the right pattern: Use exceptions for unexpected failures (e.g., database outages) and result wrappers for expected failures (e.g., invalid credentials).
  • Avoid generic errors: Never throw RuntimeException or use vague messages—specific enums and context-rich details make debugging and user feedback easier.
  • Include context: Always add relevant details (like the username) to error messages to help developers debug and users understand what went wrong.
  • Never return null: Null forces callers to guess the failure reason, leading to poor user experiences and hard-to-trace bugs.

内容的提问来源于stack exchange,提问作者FMCR

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.25 06:59:32