Комментарии (1)
🐱
claude-haiku-4.5PrepBro AI22 мар. 2026 г.(ред.)
Ответ сгенерирован нейросетью и может содержать ошибки
Как правильно выбросить исключение наверх в Java
Это важный навык, но часто делается неправильно. Существует несколько способов, и каждый имеет смысл в определённом контексте.
1. Простой throws (checked исключения)
Для checked исключений:
public class FileReader {
// Способ 1: просто пробросить через throws
public String readFile(String path) throws IOException {
BufferedReader reader = new BufferedReader(new java.io.FileReader(path));
return reader.readLine();
// Если IOException — будет выброшено в вызывающий код
}
}
// Использование
public class Application {
public static void main(String[] args) throws IOException {
FileReader reader = new FileReader();
String content = reader.readFile("data.txt"); // IOException может быть выброшен
}
}
Проблема: throws IOException распространяет ответственность выше по цепочке. Со временем вся цепочка объявляет throws IOException.
2. Обёртывание в RuntimeException (современный подход)
Для unchecked исключений. РЕКОМЕНДУЕТСЯ для большинства приложений:
public class UserRepository {
// ✅ ХОРОШО — используем unchecked исключение
public User findById(UUID id) {
try {
String query = "SELECT * FROM users WHERE id = ?";
return jdbcTemplate.queryForObject(query, new UserRowMapper(), id);
} catch (DataAccessException e) {
throw new UserNotFoundException("User not found: " + id, e);
} catch (Exception e) {
throw new RuntimeException("Database error", e);
}
}
}
// Вызывающий код НЕ обязан ловить исключение
@RestController
public class UserController {
@GetMapping("/users/{id}")
public ResponseEntity<User> getUser(@PathVariable UUID id) {
// Исключение поднимется в @ControllerAdvice
// Не нужно обрабатывать здесь
User user = userRepository.findById(id);
return ResponseEntity.ok(user);
}
}
3. Перехват и переброс с дополнительной информацией
Это очень важно для сохранения контекста:
public class OrderService {
public Order createOrder(OrderRequest request) {
try {
// Этап 1: валидация
validateOrder(request);
// Этап 2: проверка инвентаря
checkInventory(request.getProductIds());
// Этап 3: создание платежа
Payment payment = paymentService.charge(request.getAmount());
// Этап 4: сохранение
return orderRepository.save(new Order(request, payment));
} catch (PaymentException e) {
// Перехватываем конкретное исключение
// Добавляем контекст
throw new OrderCreationException(
"Payment failed for user " + request.getUserId() +
" with amount " + request.getAmount(),
e
);
} catch (InventoryException e) {
// Другой тип ошибки
throw new OutOfStockException(
"Product " + e.getProductId() + " is out of stock",
e
);
}
}
}
4. Re-throw с сохранением stack trace
// ❌ ПЛОХО — теряется оригинальный stack trace
public void processData() {
try {
loadData();
} catch (IOException e) {
throw new RuntimeException("Failed to load"); // Stack trace потерян!
}
}
// ✅ ПРАВИЛЬНО — сохраняем оригинальное исключение
public void processData() {
try {
loadData();
} catch (IOException e) {
throw new RuntimeException("Failed to load", e); // e это cause
}
}
// Другой способ
public void processData() throws IOException {
try {
loadData();
} catch (IOException e) {
// Re-throw оригинальное исключение
throw e;
}
}
5. Multi-catch для выброса разных исключений
public class DataProcessor {
public void processFile(String filename) {
try {
validateFile(filename);
readFile(filename);
parseData();
} catch (FileNotFoundException | SecurityException e) {
// Разные исключения, но обрабатываем одинаково
throw new DataProcessingException(
"Cannot access file: " + filename,
e
);
} catch (IOException e) {
throw new DataProcessingException(
"IO error while reading: " + filename,
e
);
} catch (ParseException e) {
throw new DataProcessingException(
"Invalid data format in: " + filename,
e
);
}
}
}
6. Условный выброс
Иногда нужно выбросить исключение только в определённых условиях:
public class AccountService {
public void withdraw(Account account, BigDecimal amount) {
if (amount.compareTo(BigDecimal.ZERO) <= 0) {
throw new IllegalArgumentException("Amount must be positive");
}
if (account.getBalance().compareTo(amount) < 0) {
throw new InsufficientFundsException(
"Insufficient funds: have " + account.getBalance() +
", need " + amount
);
}
account.setBalance(account.getBalance().subtract(amount));
}
}
7. Выброс в компонентах Spring
В Spring контексте исключения часто обрабатываются @ControllerAdvice:
@Service
public class UserService {
@Transactional
public User createUser(UserRequest request) {
// Любое исключение в @Transactional будет откачено
if (userRepository.existsByEmail(request.getEmail())) {
throw new EmailAlreadyExistsException("Email: " + request.getEmail());
}
if (request.getAge() < 18) {
throw new IllegalArgumentException("User must be 18+");
}
return userRepository.save(new User(request));
}
}
@RestController
@RequestMapping("/api/v1/users")
public class UserController {
@PostMapping
public ResponseEntity<UserResponse> createUser(@RequestBody UserRequest request) {
// Не ловим исключение — пусть поднимется
User user = userService.createUser(request);
return ResponseEntity.status(201).body(new UserResponse(user));
}
}
// Global exception handler
@RestControllerAdvice
public class GlobalExceptionHandler {
@ExceptionHandler(EmailAlreadyExistsException.class)
public ResponseEntity<ErrorResponse> handleEmailExists(EmailAlreadyExistsException e) {
return ResponseEntity.status(409).body(
new ErrorResponse("Conflict", e.getMessage())
);
}
@ExceptionHandler(IllegalArgumentException.class)
public ResponseEntity<ErrorResponse> handleIllegalArgument(IllegalArgumentException e) {
return ResponseEntity.badRequest().body(
new ErrorResponse("Bad Request", e.getMessage())
);
}
@ExceptionHandler(Exception.class)
public ResponseEntity<ErrorResponse> handleGeneric(Exception e) {
log.error("Unexpected error", e);
return ResponseEntity.status(500).body(
new ErrorResponse("Internal Server Error", "An error occurred")
);
}
}
8. Пример: пробросить наверх с логированием
public class EmailService {
private static final Logger log = LoggerFactory.getLogger(EmailService.class);
public void sendEmail(String to, String subject, String body) {
try {
SmtpClient client = new SmtpClient();
client.send(to, subject, body);
} catch (SmtpException e) {
// Логируем ошибку
log.error("Failed to send email to {} with subject {}", to, subject, e);
// Пробрасываем наверх (но уже залогирована)
throw new EmailSendException(
"Failed to send email to: " + to,
e
);
} catch (Exception e) {
log.error("Unexpected error while sending email", e);
throw new RuntimeException("Email service error", e);
}
}
}
9. НЕ ДЕЛАЙ ТАК (anti-patterns)
// ❌ Плохо: ловишь и ничего не делаешь
try {
database.connect();
} catch (Exception e) {
// Молчишь — ошибка потеряется
}
// ❌ Плохо: печатаешь в консоль
try {
database.connect();
} catch (Exception e) {
System.out.println("Error: " + e.getMessage()); // Нет логирования
}
// ❌ Плохо: выбрасываешь новое исключение без причины
try {
database.connect();
} catch (Exception e) {
throw new RuntimeException("Error"); // Потеял оригинальное исключение
}
// ❌ Плохо: ловишь Exception
try {
// код
} catch (Exception e) { // Слишком широко
// ловишь и OutOfMemoryError, и StackOverflowError
}
10. Best Practice итоговый пример
public class PaymentProcessor {
private static final Logger log = LoggerFactory.getLogger(PaymentProcessor.class);
public void processPayment(Payment payment) {
try {
// Бизнес-логика
validatePayment(payment);
chargeCard(payment);
updateDatabase(payment);
} catch (InvalidPaymentException e) {
// Ошибка валидации — это OK
throw e; // или оборачиваем в доменное исключение
} catch (CardDeclinedException e) {
// Карта отклонена — ожидаемая ситуация
log.warn("Card declined for user {}", payment.getUserId());
throw new PaymentFailedException("Card declined", e);
} catch (DatabaseException e) {
// Ошибка БД — логируем и выбрасываем
log.error("Database error while processing payment {}", payment.getId(), e);
throw new PaymentProcessingException(
"Failed to update payment status",
e
);
} catch (Exception e) {
// Неожиданная ошибка
log.error("Unexpected error in payment processing", e);
throw new PaymentProcessingException(
"Unexpected error",
e
);
}
}
}
Выводы
- Используй unchecked исключения (extends RuntimeException) в современном коде
- Всегда сохраняй оригинальное исключение как cause:
throw new MyException(msg, e) - Добавляй контекст при переброске: информацию о том, что делалось
- Логируй на правильном уровне: warn для ожидаемых, error для неожиданных
- Не ловишь Exception — ловишь конкретные исключения
- Используй @ControllerAdvice для глобальной обработки в Spring
- Не подавляй исключения молча — либо обработай, либо пробрось