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

С какими непроверяемыми исключениями сталкивался в работе

2.0 Middle🔥 161 комментариев
#Основы Java

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

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

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

Непроверяемые исключения в реальной работе

Краткий ответ

Непроверяемые исключения (RuntimeException и его подклассы) - это исключения которые НЕ требуют явной обработки. В реальной работе я сталкивался с множеством непроверяемых исключений: NullPointerException, IllegalArgumentException, ArrayIndexOutOfBoundsException и др.

Основные непроверяемые исключения

// Иерархия непроверяемых исключений

RuntimeException  (и все подклассы - непроверяемые)
├── NullPointerException
├── IllegalArgumentException
├── IllegalStateException
├── IndexOutOfBoundsException
│   ├── ArrayIndexOutOfBoundsException
│   └── StringIndexOutOfBoundsException
├── ClassCastException
├── NumberFormatException
├── ConcurrentModificationException
├── UnsupportedOperationException
└── ... и многие другие

Проблема 1: NullPointerException (NPE)

Реальный случай: забыл null проверку

@Service
public class UserService {
    private final UserRepository repository;
    
    public User getUser(Long id) {
        User user = repository.findById(id);  // может вернуть null
        return user.toDto();  // ❌ NPE если user == null!
    }
}

// Лучше:
public UserDto getUser(Long id) {
    User user = repository.findById(id);
    if (user == null) {
        throw new UserNotFoundException("User not found with id: " + id);
    }
    return user.toDto();
}

// Или с Optional:
public UserDto getUser(Long id) {
    return repository.findById(id)
        .map(User::toDto)
        .orElseThrow(() -> new UserNotFoundException("User " + id + " not found"));
}

Как мы ловим в реальности

@RestController
@RequestMapping("/api/users")
public class UserController {
    private final UserService service;
    
    @GetMapping("/{id}")
    public ResponseEntity<UserDto> getUser(@PathVariable Long id) {
        try {
            UserDto user = service.getUser(id);
            return ResponseEntity.ok(user);
        } catch (NullPointerException e) {
            // НЕ логируем NPE специально - это ошибка кода!
            log.error("BUG: NullPointerException in getUser", e);
            return ResponseEntity.internalServerError().build();
        } catch (UserNotFoundException e) {
            log.warn("User not found: {}", id);
            return ResponseEntity.notFound().build();
        }
    }
}

// Лучше: использовать @ExceptionHandler
@RestControllerAdvice
public class GlobalExceptionHandler {
    @ExceptionHandler(NullPointerException.class)
    public ResponseEntity<ErrorResponse> handleNpe(NullPointerException e) {
        log.error("BUG: NullPointerException - this is a code bug!", e);
        return ResponseEntity.status(500).body(
            new ErrorResponse("Internal server error", HttpStatus.INTERNAL_SERVER_ERROR)
        );
    }
    
    @ExceptionHandler(UserNotFoundException.class)
    public ResponseEntity<ErrorResponse> handleUserNotFound(UserNotFoundException e) {
        return ResponseEntity.status(404).body(
            new ErrorResponse(e.getMessage(), HttpStatus.NOT_FOUND)
        );
    }
}

Проблема 2: IllegalArgumentException

Реальный случай: неправильный параметр

@Service
public class PaymentService {
    
    public void processPayment(BigDecimal amount, String currency) {
        if (amount == null || amount.signum() <= 0) {
            throw new IllegalArgumentException(
                "Amount must be positive, got: " + amount
            );
        }
        
        if (currency == null || currency.isEmpty()) {
            throw new IllegalArgumentException(
                "Currency cannot be null or empty"
            );
        }
        
        if (!isValidCurrency(currency)) {
            throw new IllegalArgumentException(
                "Unknown currency: " + currency + ". Supported: USD, EUR, GBP"
            );
        }
        
        // Обработка платежа
        processTransaction(amount, currency);
    }
    
    private boolean isValidCurrency(String currency) {
        return currency.matches("^[A-Z]{3}$");  // ISO 4217 код
    }
}

// Использование
paymentService.processPayment(
    new BigDecimal("99.99"),  // OK
    "USD"  // OK
);

paymentService.processPayment(
    new BigDecimal("-50"),  // ❌ IllegalArgumentException
    "USD"
);

paymentService.processPayment(
    new BigDecimal("100"),
    "INVALID"  // ❌ IllegalArgumentException
);

Проблема 3: ArrayIndexOutOfBoundsException

Реальный случай: ошибка в цикле

@Service
public class DataProcessingService {
    
    // ❌ Плохо: может быть ArrayIndexOutOfBoundsException
    public void processArray(int[] data) {
        for (int i = 0; i <= data.length; i++) {  // <= вместо <
            System.out.println(data[i]);  // ArrayIndexOutOfBoundsException!
        }
    }
    
    // ✅ Правильно: нормальный цикл
    public void processArrayCorrect(int[] data) {
        for (int i = 0; i < data.length; i++) {  // правильное условие
            System.out.println(data[i]);
        }
    }
    
    // ✅ Ещё лучше: for-each
    public void processArrayBest(int[] data) {
        for (int value : data) {
            System.out.println(value);
        }
    }
    
    // ✅ Или Stream
    public void processArrayStream(int[] data) {
        Arrays.stream(data)
            .forEach(System.out::println);
    }
}

Проблема 4: ClassCastException

Реальный случай: неправильная приведение типа

@Service
public class DataService {
    
    // ❌ Плохо: может быть ClassCastException
    public String processData(Object data) {
        String str = (String) data;  // если data не String -> ошибка!
        return str.toUpperCase();
    }
    
    // ✅ Правильно: проверка типа
    public String processDataSafe(Object data) {
        if (data instanceof String) {
            String str = (String) data;  // теперь безопасно
            return str.toUpperCase();
        } else {
            throw new IllegalArgumentException(
                "Expected String, got: " + data.getClass().getName()
            );
        }
    }
    
    // ✅ Java 16+: pattern matching
    public String processDataPattern(Object data) {
        if (data instanceof String str) {  // pattern matching
            return str.toUpperCase();
        } else {
            throw new IllegalArgumentException(
                "Expected String, got: " + data.getClass().getName()
            );
        }
    }
}

Проблема 5: NumberFormatException

Реальный случай: парсинг числа

@RestController
public class ApiController {
    
    // ❌ Плохо: NumberFormatException если строка не число
    @GetMapping("/numbers/{id}")
    public ResponseEntity<Integer> getNumber(@PathVariable String id) {
        int number = Integer.parseInt(id);  // может быть ошибка
        return ResponseEntity.ok(number);
    }
    
    // ✅ Правильно: обработка исключения
    @GetMapping("/numbers/{id}")
    public ResponseEntity<Integer> getNumberSafe(@PathVariable String id) {
        try {
            int number = Integer.parseInt(id);
            return ResponseEntity.ok(number);
        } catch (NumberFormatException e) {
            return ResponseEntity.badRequest().build();
        }
    }
    
    // ✅ Ещё лучше: валидация параметра
    @GetMapping("/numbers/{id}")
    public ResponseEntity<Integer> getNumberBest(
        @PathVariable @Min(0) @Max(Integer.MAX_VALUE) Integer id) {
        return ResponseEntity.ok(id);
    }
}

Проблема 6: ConcurrentModificationException

Реальный случай: изменение коллекции во время итерации

@Service
public class ListProcessingService {
    
    // ❌ Плохо: ConcurrentModificationException
    public void removeInactive(List<User> users) {
        for (User user : users) {
            if (!user.isActive()) {
                users.remove(user);  // ❌ модифицируем во время итерации
            }
        }
    }
    
    // ✅ Правильно: использовать Iterator
    public void removeInactiveSafe(List<User> users) {
        Iterator<User> iterator = users.iterator();
        while (iterator.hasNext()) {
            User user = iterator.next();
            if (!user.isActive()) {
                iterator.remove();  // правильно
            }
        }
    }
    
    // ✅ Ещё лучше: создать новый список
    public void removeInactiveBest(List<User> users) {
        users.removeIf(user -> !user.isActive());
    }
    
    // ✅ Или с Stream
    public List<User> getActive(List<User> users) {
        return users.stream()
            .filter(User::isActive)
            .collect(toList());
    }
}

Проблема 7: IllegalStateException

Реальный случай: неправильное состояние объекта

public class Order {
    private OrderStatus status;
    private LocalDateTime shippedDate;
    
    // ❌ Плохо: не проверяем состояние
    public void shipOrder(LocalDateTime date) {
        this.shippedDate = date;  // может быть в любом статусе
        this.status = OrderStatus.SHIPPED;
    }
    
    // ✅ Правильно: проверяем состояние
    public void shipOrder(LocalDateTime date) {
        if (this.status != OrderStatus.PENDING) {
            throw new IllegalStateException(
                "Order can only be shipped from PENDING state, current: " + status
            );
        }
        this.shippedDate = date;
        this.status = OrderStatus.SHIPPED;
    }
    
    // ❌ Плохо: нет проверки
    public void cancel() {
        this.status = OrderStatus.CANCELLED;  // можно отменить любой заказ?
    }
    
    // ✅ Правильно: проверяем
    public void cancel() {
        if (this.status == OrderStatus.SHIPPED) {
            throw new IllegalStateException(
                "Cannot cancel SHIPPED order"
            );
        }
        this.status = OrderStatus.CANCELLED;
    }
}

Best Practices при работе с непроверяемыми исключениями

1. Не ловить RuntimeException без причины

// ❌ Плохо: ловим все и молчим
try {
    // код
} catch (RuntimeException e) {
    // молчим
}

// ✅ Правильно: ловим специфичные исключения
try {
    int number = Integer.parseInt(input);
} catch (NumberFormatException e) {
    log.warn("Invalid number format: {}", input);
    return null;
}

2. Проверяй параметры методов

// ❌ Плохо: нет проверок
public void transfer(BigDecimal amount, Account from, Account to) {
    from.withdraw(amount);
    to.deposit(amount);
}

// ✅ Правильно: проверяем параметры
public void transfer(BigDecimal amount, Account from, Account to) {
    if (amount == null || amount.signum() <= 0) {
        throw new IllegalArgumentException("Amount must be positive");
    }
    if (from == null) {
        throw new IllegalArgumentException("Source account cannot be null");
    }
    if (to == null) {
        throw new IllegalArgumentException("Target account cannot be null");
    }
    if (from.equals(to)) {
        throw new IllegalArgumentException("Accounts cannot be the same");
    }
    
    from.withdraw(amount);
    to.deposit(amount);
}

// ✅ Или используй @NotNull, @NotEmpty и т.д.
public void transfer(
    @NotNull @DecimalMin("0.01") BigDecimal amount,
    @NotNull Account from,
    @NotNull Account to) {
    // бизнес-логика
}

3. Создавай свои исключения

// ❌ Плохо: используем стандартные
throw new IllegalArgumentException("User not found");

// ✅ Правильно: свои исключения
public class UserNotFoundException extends RuntimeException {
    public UserNotFoundException(String message) {
        super(message);
    }
    
    public UserNotFoundException(String message, Throwable cause) {
        super(message, cause);
    }
}

public class InvalidOrderStateException extends RuntimeException {
    public InvalidOrderStateException(String message) {
        super(message);
    }
}

// Использование
throw new UserNotFoundException("User not found with id: " + id);

Статистика непроверяемых исключений в моей практике

Топ-5 непроверяемых исключений которые я фиксил:

1. NullPointerException          ~40%
   (обычно забыли проверку null)

2. IllegalArgumentException      ~25%
   (неправильные параметры методов)

3. NumberFormatException         ~15%
   (парсинг числовых строк)

4. IllegalStateException         ~12%
   (неправильное состояние объекта)

5. ConcurrentModificationException ~8%
   (изменение коллекции во время итерации)

Вывод

Непроверяемые исключения (RuntimeException) - это:

  1. Признак ошибки в коде, не ошибки в данных
  2. НЕ требуют явной обработки (в отличие от checked)
  3. Должны предотвращаться (валидация входных данных)
  4. Помогают отлавливать bugs если их не ловить
  5. Часто игнорируются без нужной причины

Практический совет: Не ловите RuntimeException - лучше предотвращайте их через валидацию и проверки. Если RuntimeException всё же вылетела - это признак bugs в коде, а не нормальный ход выполнения.

С какими непроверяемыми исключениями сталкивался в работе | PrepBro