← Назад к вопросам
Как обратиться к исключению без нативного запроса
2.0 Middle🔥 171 комментариев
#ООП#Основы Java
Комментарии (1)
🐱
claude-haiku-4.5PrepBro AI22 мар. 2026 г.(ред.)
Ответ сгенерирован нейросетью и может содержать ошибки
# Обработка исключений в Java без нативного запроса
Этот вопрос о правильном обращении с исключениями в Java приложении. Рассмотрю best practices.
1. Базовая обработка исключений
Плохо (скрывают ошибку):
try {
// Какой-то код
userRepository.save(user);
} catch (Exception e) {
// Молча игнорируем ошибку!
System.out.println("Error"); // Не достаточно
}
Правильно (логируем и переработаем):
try {
userRepository.save(user);
} catch (DataIntegrityViolationException e) {
log.error("Failed to save user due to constraint violation", e);
throw new ApplicationException("User with this email already exists", e);
} catch (Exception e) {
log.error("Unexpected error while saving user", e);
throw new ApplicationException("Failed to save user", e);
}
2. Иерархия исключений (Custom Exceptions)
Правильный подход:
// Базовое исключение приложения
public abstract class ApplicationException extends RuntimeException {
private final String errorCode;
public ApplicationException(String message, String errorCode) {
super(message);
this.errorCode = errorCode;
}
public ApplicationException(String message, String errorCode, Throwable cause) {
super(message, cause);
this.errorCode = errorCode;
}
public String getErrorCode() {
return errorCode;
}
}
// Специфичные исключения
public class UserNotFoundException extends ApplicationException {
public UserNotFoundException(Long userId) {
super(
String.format("User with ID %d not found", userId),
"USER_NOT_FOUND"
);
}
}
public class InvalidUserDataException extends ApplicationException {
public InvalidUserDataException(String message) {
super(message, "INVALID_USER_DATA");
}
}
public class InsufficientFundsException extends ApplicationException {
public InsufficientFundsException(BigDecimal required, BigDecimal available) {
super(
String.format("Insufficient funds. Required: %s, Available: %s", required, available),
"INSUFFICIENT_FUNDS"
);
}
}
3. Обработка на уровне Service
@Service
public class UserService {
@Autowired
private UserRepository userRepository;
public User getUserById(Long id) {
return userRepository.findById(id)
.orElseThrow(() -> new UserNotFoundException(id));
}
@Transactional
public User createUser(CreateUserRequest request) {
if (userRepository.existsByEmail(request.getEmail())) {
throw new InvalidUserDataException("Email already registered");
}
try {
User user = new User(request.getName(), request.getEmail());
return userRepository.save(user);
} catch (DataIntegrityViolationException e) {
log.error("Database constraint violation", e);
throw new InvalidUserDataException("Failed to create user: " + e.getMessage());
}
}
}
4. Глобальная обработка исключений (@ControllerAdvice)
@RestControllerAdvice
@Slf4j
public class GlobalExceptionHandler {
@ExceptionHandler(UserNotFoundException.class)
public ResponseEntity<ErrorResponse> handleUserNotFound(
UserNotFoundException ex, HttpServletRequest request) {
ErrorResponse error = ErrorResponse.builder()
.timestamp(LocalDateTime.now())
.status(HttpStatus.NOT_FOUND.value())
.error("Not Found")
.message(ex.getMessage())
.path(request.getRequestURI())
.errorCode(ex.getErrorCode())
.build();
log.warn("User not found: {}", ex.getMessage());
return ResponseEntity.status(HttpStatus.NOT_FOUND).body(error);
}
@ExceptionHandler(InvalidUserDataException.class)
public ResponseEntity<ErrorResponse> handleInvalidData(
InvalidUserDataException ex, HttpServletRequest request) {
ErrorResponse error = ErrorResponse.builder()
.timestamp(LocalDateTime.now())
.status(HttpStatus.BAD_REQUEST.value())
.error("Bad Request")
.message(ex.getMessage())
.path(request.getRequestURI())
.errorCode(ex.getErrorCode())
.build();
log.warn("Invalid data provided: {}", ex.getMessage());
return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(error);
}
@ExceptionHandler(InsufficientFundsException.class)
public ResponseEntity<ErrorResponse> handleInsufficientFunds(
InsufficientFundsException ex, HttpServletRequest request) {
ErrorResponse error = ErrorResponse.builder()
.timestamp(LocalDateTime.now())
.status(HttpStatus.PAYMENT_REQUIRED.value())
.error("Payment Required")
.message(ex.getMessage())
.path(request.getRequestURI())
.errorCode(ex.getErrorCode())
.build();
log.warn("Insufficient funds: {}", ex.getMessage());
return ResponseEntity.status(HttpStatus.PAYMENT_REQUIRED).body(error);
}
@ExceptionHandler(Exception.class)
public ResponseEntity<ErrorResponse> handleGenericException(
Exception ex, HttpServletRequest request) {
ErrorResponse error = ErrorResponse.builder()
.timestamp(LocalDateTime.now())
.status(HttpStatus.INTERNAL_SERVER_ERROR.value())
.error("Internal Server Error")
.message("An unexpected error occurred")
.path(request.getRequestURI())
.errorCode("INTERNAL_ERROR")
.build();
log.error("Unexpected error", ex);
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(error);
}
}
5. ErrorResponse DTO
@Data
@Builder
public class ErrorResponse {
private LocalDateTime timestamp;
private int status;
private String error;
private String message;
private String path;
private String errorCode;
private List<FieldError> fieldErrors; // для валидации
@Data
public static class FieldError {
private String field;
private String rejectedValue;
private String message;
}
}
6. Использование @Transactional с обработкой исключений
@Service
public class PaymentService {
@Autowired
private PaymentRepository paymentRepository;
@Autowired
private WalletService walletService;
@Transactional(rollbackFor = Exception.class)
public Payment processPayment(PaymentRequest request) {
try {
// Проверка баланса
Wallet wallet = walletService.getWallet(request.getUserId());
if (wallet.getBalance().compareTo(request.getAmount()) < 0) {
throw new InsufficientFundsException(
request.getAmount(),
wallet.getBalance()
);
}
// Обработка платежа
Payment payment = new Payment(request);
walletService.debit(request.getUserId(), request.getAmount());
return paymentRepository.save(payment);
} catch (InsufficientFundsException e) {
// Специфичная обработка
throw e; // Пробросим дальше
} catch (Exception e) {
// Неожиданная ошибка
log.error("Payment processing failed", e);
throw new ApplicationException("Failed to process payment", "PAYMENT_FAILED", e);
}
}
}
7. Try-Catch с finally для ресурсов
Старый способ (не использовать):
InputStream is = null;
try {
is = new FileInputStream("file.txt");
// работа с файлом
} catch (IOException e) {
log.error("Error reading file", e);
} finally {
if (is != null) {
try {
is.close();
} catch (IOException e) {
log.error("Error closing stream", e);
}
}
}
Правильный способ (try-with-resources):
try (InputStream is = new FileInputStream("file.txt")) {
// работа с файлом
// Ресурс автоматически закроется
} catch (IOException e) {
log.error("Error reading file", e);
}
8. Логирование исключений
Правильная стратегия:
@Service
public class LoggingService {
// ❌ Не делай так:
public void badExample() {
try {
// код
} catch (Exception e) {
e.printStackTrace(); // Плохо! Печатает в stderr
}
}
// ❌ Не делай так:
public void alsoBad() {
try {
// код
} catch (Exception e) {
System.out.println("Error: " + e.getMessage()); // Теряем stack trace
}
}
// ✅ Правильно:
public void goodExample() {
try {
// код
} catch (SpecificException e) {
log.warn("Expected error occurred", e); // warn + исключение
} catch (Exception e) {
log.error("Unexpected error", e); // error + исключение
}
}
}
9. Checked vs Unchecked исключения
// ❌ Плохо (checked exception)
public class BadService {
public void doSomething() throws IOException, SQLException, ParseException {
// много checked exceptions в сигнатуре
}
}
// ✅ Правильно (unchecked exceptions)
public class GoodService {
public void doSomething() {
try {
// код
} catch (IOException e) {
throw new ApplicationException("IO failed", "IO_ERROR", e);
} catch (SQLException e) {
throw new ApplicationException("Database failed", "DB_ERROR", e);
}
}
}
10. Optional вместо исключений для отсутствующих значений
Менее идиоматично:
public User findUser(Long id) throws UserNotFoundException {
return userRepository.findById(id)
.orElseThrow(() -> new UserNotFoundException(id));
}
Более идиоматично:
public Optional<User> findUser(Long id) {
return userRepository.findById(id);
}
// Использование:
service.findUser(1)
.ifPresentOrElse(
user -> System.out.println("Found: " + user),
() -> System.out.println("Not found")
);
// Или:
User user = service.findUser(1)
.orElseThrow(() -> new UserNotFoundException(1));
Best Practices Summary
- ✅ Создавай иерархию custom exceptions
- ✅ Используй @ControllerAdvice для глобальной обработки
- ✅ Логируй с полным stack trace (log.error(..., exception))
- ✅ Переходи от checked к unchecked exceptions
- ✅ Используй try-with-resources для автоматического закрытия ресурсов
- ✅ Не скрывай исключения (catch и ничего не делать — плохо)
- ✅ Используй Optional для значений которых может не быть
- ✅ Всегда включай причину исключения (cause) при переброске
- ✅ Различай warn (ожидаемые ошибки) и error (неожиданные)
- ✅ Включай достаточно информации в сообщение об ошибке для диагностики
Правильная обработка исключений делает код более надёжным, понятным и простым в отладке.