← Назад к вопросам

С чего начинаются исключения

1.3 Junior🔥 171 комментариев
#ORM и Hibernate#Spring Boot и Spring Data#Базы данных и SQL

Комментарии (1)

🐱
claude-haiku-4.5PrepBro AI23 мар. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

Иерархия исключений в Java: начало со Throwable

Все исключения в Java начинаются с класса Throwable — это корень иерархии всех ошибок и исключений. Это фундаментальное знание, которое критично для написания правильного обработчика ошибок.

Иерархия Throwable

Throwable (java.lang.Throwable)
├── Error (java.lang.Error)
│   ├── OutOfMemoryError
│   ├── StackOverflowError
│   ├── LinkageError
│   ├── VirtualMachineError
│   └── ...
│
└── Exception (java.lang.Exception)
    ├── Checked Exceptions (должны быть обработаны)
    │   ├── IOException
    │   ├── SQLException
    │   ├── InterruptedException
    │   └── ...
    │
    └── RuntimeException (Unchecked Exceptions)
        ├── NullPointerException
        ├── IllegalArgumentException
        ├── IndexOutOfBoundsException
        ├── ArithmeticException
        └── ...

Что такое Throwable

// Throwable — базовый класс для всего что можно "выбросить"
public class Throwable implements Serializable {
    
    // Сообщение об ошибке
    private String message;
    
    // Причина (что вызвало эту ошибку)
    private Throwable cause;
    
    // Stack trace для отладки
    private StackTraceElement[] stackTrace;
    
    // Подавленные исключения (try-with-resources)
    private List<Throwable> suppressedExceptions;
    
    // Основные методы
    public String getMessage() { return message; }
    public Throwable getCause() { return cause; }
    public StackTraceElement[] getStackTrace() { return stackTrace; }
    public void printStackTrace() { /* печать в консоль */ }
    public String toString() { /* Throwable класс + сообщение */ }
}

Error vs Exception

Error — это проблемы с JVM, которые обычно нельзя обработать:

public class JavaVirtualMachineErrors {
    
    // OutOfMemoryError — когда кончилась память
    public void memoryError() {
        List<byte[]> hugeList = new ArrayList<>();
        while (true) {
            hugeList.add(new byte[1024 * 1024]);  // 1MB
            // java.lang.OutOfMemoryError: Java heap space
        }
    }
    
    // StackOverflowError — рекурсия без базового случая
    public void recursionError(int n) {
        System.out.println(n);
        recursionError(n + 1);  // Бесконечная рекурсия
        // java.lang.StackOverflowError
    }
    
    // LinkageError — проблемы при загрузке классов
    public void linkageError() {
        // Возникает когда класс не может быть загружен
        // обычно это проблема конфигурации, не кода
    }
}

// Не ловим Error! Это означает проблему с JVM
// ❌ try {
//     recursionError(0);
// } catch (Error e) {  // ПЛОХО
//     e.printStackTrace();
// }

Exception — ошибки приложения, которые можно обработать:

public class ExceptionHandling {
    
    // Checked Exception — ДОЛЖНА быть обработана
    public void readFile(String filename) throws IOException {
        // Либо бросаем дальше (throws), либо обрабатываем (try-catch)
        FileReader reader = new FileReader(filename);
        reader.close();
    }
    
    // Unchecked Exception (RuntimeException)
    public void runtimeError(String[] args) {
        String value = args[0];  // Может быть ArrayIndexOutOfBoundsException
        Integer number = Integer.parseInt(value);  // Может быть NumberFormatException
    }
}

Checked vs Unchecked Exceptions

public class ExceptionTypes {
    
    // CHECKED (должны быть обработаны компилятором)
    public void checkedExceptionExample() {
        try {
            Thread.sleep(1000);  // throws InterruptedException
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
        }
        
        try {
            InputStream is = new FileInputStream("file.txt");
            // throws IOException
        } catch (IOException e) {
            System.err.println("File not found: " + e.getMessage());
        }
    }
    
    // UNCHECKED (не требуют обработки, но хорошо бы обработать)
    public void uncheckedExceptionExample() {
        String value = null;
        
        // Может быть NullPointerException
        int length = value.length();
        
        // Может быть ClassCastException
        Object obj = new Integer(123);
        String str = (String) obj;
        
        // Может быть IllegalArgumentException
        if (value == null) {
            throw new IllegalArgumentException("Value cannot be null");
        }
    }
}

Правильная обработка исключений

@Service
public class BankTransferService {
    
    @Autowired
    private AccountRepository accountRepository;
    
    @Autowired
    private AuditLogService auditLog;
    
    public void transferMoney(Long fromId, Long toId, 
            BigDecimal amount) throws InsufficientFundsException {
        
        // Обработка Checked Exception
        try {
            // Операция которая может выбросить IOException
            validateTransferWithExternalService(fromId, toId, amount);
        } catch (IOException e) {
            // IOException — это ошибка сети, нужно обработать
            auditLog.error("External service unavailable", e);
            throw new ServiceUnavailableException(
                "Cannot validate transfer", e);
        }
        
        // Обработка Unchecked Exception
        try {
            Account from = accountRepository.findById(fromId)
                .orElseThrow(() -> new AccountNotFoundException(
                    "From account not found"));
            
            Account to = accountRepository.findById(toId)
                .orElseThrow(() -> new AccountNotFoundException(
                    "To account not found"));
            
            if (amount.compareTo(BigDecimal.ZERO) <= 0) {
                throw new IllegalArgumentException(
                    "Amount must be positive");
            }
            
            if (from.getBalance().compareTo(amount) < 0) {
                throw new InsufficientFundsException(
                    from.getId(), amount);
            }
            
            // Выполняем передачу
            from.withdraw(amount);
            to.deposit(amount);
            accountRepository.saveAll(List.of(from, to));
            
        } catch (DataAccessException e) {
            // Ошибка БД — логируем и переводим в более ясную форму
            auditLog.error("Database error during transfer", e);
            throw new TransferFailedException(
                "Failed to complete transfer", e);
        }
    }
    
    private void validateTransferWithExternalService(
            Long fromId, Long toId, BigDecimal amount) 
            throws IOException {
        // Может выбросить IOException (Checked)
    }
}

Custom Exceptions

// Проверяемое исключение (Checked)
public class InsufficientFundsException extends Exception {
    private Long accountId;
    private BigDecimal requiredAmount;
    
    public InsufficientFundsException(Long accountId, 
            BigDecimal requiredAmount) {
        super(String.format(
            "Account %d has insufficient funds. Required: %s",
            accountId, requiredAmount));
        this.accountId = accountId;
        this.requiredAmount = requiredAmount;
    }
    
    public Long getAccountId() { return accountId; }
    public BigDecimal getRequiredAmount() { return requiredAmount; }
}

// Непроверяемое исключение (Unchecked)
public class AccountNotFoundException extends RuntimeException {
    private Long accountId;
    
    public AccountNotFoundException(String message) {
        super(message);
    }
    
    public AccountNotFoundException(String message, Throwable cause) {
        super(message, cause);
        // Всегда сохраняй причину для лучшей отладки
    }
}

// Специальное исключение для сервиса
public class TransferFailedException extends RuntimeException {
    public TransferFailedException(String message, Throwable cause) {
        super(message, cause);
    }
}

Stack Trace и Debugging

public class ExceptionAnalysis {
    
    public static void analyzeException(Throwable e) {
        // Основная информация
        System.out.println("Exception: " + e.getClass().getSimpleName());
        System.out.println("Message: " + e.getMessage());
        
        // Stack trace
        StackTraceElement[] stackTrace = e.getStackTrace();
        for (StackTraceElement element : stackTrace) {
            System.out.println("  at " + element);
            // Filename.java:123
        }
        
        // Цепочка исключений (cause)
        Throwable cause = e.getCause();
        if (cause != null) {
            System.out.println("Caused by: " + cause.getClass().getSimpleName());
            System.out.println(cause.getMessage());
        }
    }
    
    public static void main(String[] args) {
        try {
            throw new NullPointerException("Value is null");
        } catch (Exception e) {
            analyzeException(e);
        }
    }
}

// Вывод:
// Exception: NullPointerException
// Message: Value is null
//   at ExceptionAnalysis.main(ExceptionAnalysis.java:45)

Лучшие практики

  1. Никогда не лови Exception или Throwable просто так

    // ❌ ПЛОХО
    try {
        riskyOperation();
    } catch (Exception e) {  // Ловишь всё
        // Может скрыть серьёзные ошибки
    }
    
    // ✅ ХОРОШО
    try {
        riskyOperation();
    } catch (SpecificException e) {  // Ловишь конкретное
        handleSpecificError(e);
    }
    
  2. Всегда сохраняй cause (причину)

    try {
        databaseOperation();
    } catch (SQLException e) {
        throw new DataAccessException("Failed to access DB", e);  // Передаём e
    }
    
  3. Логируй полную информацию об ошибке

    logger.error("Operation failed", exception);  // Включает stack trace
    
  4. Не выбрасывай generic Exception

    // ❌ ПЛОХО
    throw new Exception("Something went wrong");
    
    // ✅ ХОРОШО
    throw new InvalidOperationException("Cannot process negative amount");
    

Иерархия в Spring

Spring вводит свои иерархии исключений:

DataAccessException (Unchecked)
├── InvalidDataAccessResourceUsageException
├── PermissionDeniedDataAccessException
├── DataRetrievalFailureException
└── ...

HttpClientErrorException (Unchecked)
├── HttpClientErrorException.BadRequest (400)
├── HttpClientErrorException.Unauthorized (401)
├── HttpClientErrorException.NotFound (404)
└── ...

Ключевой момент: Все исключения в Java происходят от Throwable, но различаются тем, как и когда их нужно обрабатывать. Error — это обычно конец программы, Exception — это то, что приложение может попытаться исправить.