Раздел подготовки

Java: вопросы для собеседования QA

Java часто нужна AQA-кандидатам: интервьюер смотрит, понимаете ли вы язык автотестов, а не просто копируете готовые шаблоны.

100 вопросов 30 базовых 3 уровня: Junior, Middle, Senior

Что закрывает этот раздел

Подготовиться к Java-вопросам для AQA и fullstack QA интервью.

  • объяснять ООП и коллекции
  • читать тестовый код
  • разбирать исключения и Stream API

С чего начать

Если времени мало, начни с базовых вопросов: они чаще всего помогают пройти первый технический фильтр и показать структуру мышления.

  1. Что означает static в Java?
  2. В чём разница между equals и == (Java)?
  3. Чем отличаются примитивные и ссылочные типы в Java?
  4. В чём разница между Maven и Gradle для Java тест-проектов?
  5. Как устроены основные коллекции в Java и когда какую использовать?
  6. Как устроены исключения в Java и как вы их обрабатываете?
  7. В чём разница между JUnit vs TestNG: сравнение Java testing frameworks?
  8. Как вы используете Mockito для моков и заглушек в Java-тестах?

Вопросы и ответы

Что означает static в Java?

Junior

static принадлежит классу, а не конкретному объекту. Статические методы и поля существуют в единственном экземпляре, не зависящем от создания объекта.

Подходит для утилит, констант и фабричных методов, которые не зависят от состояния экземпляра.

Из практики: WebDriver instance часто делают static в JUnit-классах — это одна из причин гонок при параллельном запуске. Для изоляции лучше использовать ThreadLocal<WebDriver>.

Частые ошибки

  • Хранят изменяемое глобальное состояние в static.

Дополнительные вопросы

  • Чем опасен static в параллельных тестах?

В чём разница между equals и == (Java)?

Junior

`==` для примитивов (int, char, boolean) сравнивает **значения**. Для объектов `==` сравнивает **ссылки** — указывают ли переменные на один и тот же объект в памяти. `equals()` — смысловое равенство по правилам класса.

Для строк и value-объектов всегда используют `equals()`: `new String("abc") == new String("abc")` вернёт `false` (разные объекты), а `new String("abc").equals(new String("abc"))` — `true`.

Из практики: в автотестах `==` даёт «плавающие» падения — на литералах из пула строк `"abc" == "abc"` бывает `true` из-за string interning, а на объектах, созданных через `new`, — `false`.

Частые ошибки

  • Сравнивают объекты через ==.

Дополнительные вопросы

  • Почему equals и hashCode должны идти парой?

Чем отличаются примитивные и ссылочные типы в Java?

Junior

В Java есть примитивные типы (например, int, long, boolean) и ссылочные типы (классы, массивы, строки, коллекции).

Примитивы хранят само значение, а ссылочные типы хранят ссылку на объект в памяти.

Из практики: понимание этой разницы помогает избежать ошибок с null и сравнением объектов в тестовом коде.

Частые ошибки

  • Путают размеры примитивов (например, int vs long).
  • Неправильное сравнение строк через ==.

Дополнительные вопросы

  • Когда использовать StringBuilder vs StringBuffer?
  • Как работает String pool?

В чём разница между Maven и Gradle для Java тест-проектов?

Middle

Maven использует XML (pom.xml) с жёсткими конвенциями — просто и предсказуемо, хорошо для стандартных проектов. Gradle использует Groovy/Kotlin DSL — более гибкий и мощный, но требует более глубокого понимания.

Gradle быстрее Maven за счёт инкрементальных сборок и build cache — повторно пересобирает только изменённые модули. Maven пересобирает всё согласно lifecycle.

Из практики: для большинства тестовых проектов Maven проще поддерживать команде. Gradle беру, когда важна скорость сборки в монорепо или нужна нестандартная логика.

Частые ошибки

  • Конфликты зависимостей без dependency management.
  • Медленные builds без кэширования.

Дополнительные вопросы

  • Предпочитаете Maven или Gradle?
  • Как решаете dependency conflicts?

Как устроены основные коллекции в Java и когда какую использовать?

Middle

Основные коллекции: ArrayList — список с быстрым доступом по индексу O(1), LinkedList — быстрые вставки/удаления. HashMap — словарь с O(1) доступом по ключу, LinkedHashMap — с сохранением порядка. HashSet — уникальные элементы, TreeSet — уникальные в сортированном порядке.

Выбор по задаче: нужен порядок → ArrayList/LinkedList; нужна уникальность → HashSet; нужен поиск по ключу → HashMap; нужна очередь → ArrayDeque.

Из практики: правильный выбор коллекции сильно влияет на читаемость и скорость кода тестов — использую Map для тест-данных по ключу и Set для проверки уникальности ответов.

Частые ошибки

  • Неправильный выбор коллекции для задачи.
  • Забывают переопределить equals/hashCode.

Дополнительные вопросы

  • Внутренняя реализация HashMap?
  • Когда использовать ConcurrentHashMap?

Как устроены исключения в Java и как вы их обрабатываете?

Middle

Иерархия: всё наследуется от Throwable → Error (фатальные, не ловим) и Exception; внутри Exception выделяют RuntimeException и его наследников.

Checked (проверяемые) обязаны быть объявлены в throws или обработаны, unchecked (RuntimeException) — нет; это часть контракта метода.

Механизм: try/catch/finally для перехвата и гарантированной очистки (или try-with-resources), throw — выброс, throws — объявление; можно создавать свои классы-наследники Exception/RuntimeException.

Из практики: плохая обработка исключений часто делает тесты «зелёными», хотя бизнес-ошибка реально произошла.

Частые ошибки

  • Проглатывание исключений (пустой catch).
  • Неправильное использование finally.

Дополнительные вопросы

  • Когда создавать checked vs unchecked exception?
  • Как работает suppressed exceptions?

В чём разница между JUnit vs TestNG: сравнение Java testing frameworks?

Middle

JUnit 5 — де-факто стандарт Java-экосистемы, нативная интеграция со Spring Boot Test, Maven Surefire, Gradle. Модульная архитектура: Jupiter (API), Vintage (JUnit 4 совместимость), Platform (движок). Аннотации: @Test, @BeforeEach, @ParameterizedTest, @ExtendWith.

TestNG даёт из коробки: группировка тестов (@Test(groups=...)), зависимости между тестами (@Test(dependsOnMethods=...)), параллельный запуск на уровне конфигурации (testng.xml), data providers (@DataProvider). Широко используется в Selenium-проектах и legacy корпоративных кодовых базах.

Из практики: для новых проектов беру JUnit 5 — проще и лучше поддерживается сообществом. TestNG выбираю, когда нужны сложные зависимости между тестами или проект уже на Selenium Grid с testng.xml. Принципиальная разница: JUnit-тесты независимы по дизайну, TestNG допускает явные зависимости между тестами.

Частые ошибки

  • Смешивание JUnit 4 и JUnit 5 annotations.
  • Неправильное использование dependencies в TestNG.

Дополнительные вопросы

  • Опыт с JUnit 5 возможности?
  • Как организуете parallel execution в TestNG?

Как вы используете Mockito для моков и заглушек в Java-тестах?

Middle

Mockito позволяет создавать моки через mock() или аннотацию @Mock с @ExtendWith(MockitoExtension.class). Поведение задаю через when(mock.method()).thenReturn(value) или doReturn(value).when(mock).method().

Проверяю взаимодействия через verify(mock).method(args) — был ли метод вызван и с какими аргументами. Для частичных моков использую spy(), для ArgumentCaptor — захватываю аргументы вызова.

Из практики: подменяю только то, что реально мешает тесту. Избыточные моки делают тесты хрупкими и слабо связанными с реальным поведением.

Частые ошибки

  • Over-mocking вместо интеграция тестов.
  • Неправильное использование argument matchers.

Дополнительные вопросы

  • Когда используете @Mock vs @Spy?
  • Опыт с PowerMock для static methods?

Как вы применяете принципы ООП в Java на практике?

Middle

Инкапсуляция в Java: приватные поля с геттерами/сеттерами, управление доступом через private/protected/public. Наследование: `extends` для классов (одиночное), `implements` для интерфейсов (множественное). `final class` запрещает наследование.

Полиморфизм: переопределение методов (@Override) и перегрузка. `instanceof` для проверки типа. Абстракция: `abstract class` (может иметь реализацию и состояние) vs `interface` (с Java 8 допускает default-методы). Для поведенческих контрактов без состояния — интерфейс, для базовой реализации — абстрактный класс.

Из практики: в автотестах применяю все четыре принципа постоянно — BasePage как абстрактный класс (инкапсуляция локаторов, базовое поведение), конкретные PageObject наследуют от него, WebDriver передаётся через конструктор (инкапсуляция). Стратегии авторизации — разные реализации одного интерфейса (полиморфизм).

Частые ошибки

  • Злоупотребление наследованием вместо композиции.
  • Нарушение принципов SOLID.

Дополнительные вопросы

  • Почему в Java нет множественного наследования классов?
  • Когда использовать абстрактный класс vs интерфейс?

Чем отличаются Jackson и Gson и что выбрать в Java-проекте?

Middle

Jackson — стандарт в Java-экосистеме, богатый API: ObjectMapper, аннотации (@JsonProperty, @JsonIgnore), поддержка полиморфизма, отличная интеграция со Spring. Производительность выше, чем у Gson.

Gson — простая библиотека от Google, минимум настроек, удобна для небольших проектов без Spring. Меньше аннотаций из коробки, хуже поддерживает сложные типы.

Из практики: в Spring-проектах Jackson уже включён по умолчанию, поэтому выбор очевиден. Gson подходит для небольших утилит или Android, где нет Spring-зависимостей.

Частые ошибки

  • Игнорирование null handling strategy.
  • Circular dependencies в моделях.

Дополнительные вопросы

  • Jackson vs GSON - когда что выбираете?
  • Опыт с custom serializers?

Как вы используете assertions, assumptions и timeout в JUnit 5?

Middle

Основные методы Assertions: `assertEquals(expected, actual, message)`, `assertThrows(Exception.class, () -> code)`, `assertAll(()-> assert1, ()-> assert2)` — группирует несколько проверок без остановки на первой ошибке.

Assumptions: `assumeTrue(condition)` — пропускает тест если условие не выполнено (не падает, а SKIPPED). Таймаут: `assertTimeout(Duration.ofSeconds(2), () -> code)` — защита от зависания.

Из практики: `assertAll` незаменим когда нужно проверить несколько полей объекта — вижу все расхождения сразу, а не останавливаюсь на первом несовпадении.

Частые ошибки

  • assertTimeoutPreemptively с thread-local variables.
  • Отсутствие message в assertions.

Дополнительные вопросы

  • Как выбираете между assertions/assumptions?
  • Опыт с custom assertion libraries?

Какие базовые аннотации JUnit 5 важно знать?

Middle

Основные аннотации JUnit 5: @Test — метод тест, @BeforeEach / @AfterEach — до/после каждого теста, @BeforeAll / @AfterAll — один раз для всего класса (должны быть static).

Дополнительные: @DisplayName — читаемое имя теста в отчёте, @Disabled — пропуск теста с причиной, @Tag — группировка для избирательного запуска, @Nested — вложенные классы тестов.

Из практики: @DisplayName сильно помогает в отчётах — вместо testUserCannotLoginWithWrongPassword вижу «Пользователь не может войти с неверным паролем».

Частые ошибки

  • Смешивание JUnit 4 и 5 аннотаций.
  • Неправильное использование static для PER_CLASS.

Дополнительные вопросы

  • Как мигрировали с JUnit 4 на 5?
  • Опыт с custom test engines?

Как вы применяете аннотации жизненного цикла в JUnit 5?

Middle

Аннотации жизненного цикла в JUnit 5: @BeforeAll — выполняется один раз перед всеми тестами класса (static-метод); @BeforeEach — перед каждым тестом; @AfterEach — после каждого теста; @AfterAll — один раз после всех тестов (static).

Разделяю общую подготовку (@BeforeAll для тяжёлых ресурсов: запуск контейнера, соединение с БД) и подготовку на каждый тест (@BeforeEach для состояния и данных) — это сохраняет изоляцию.

Из практики: корректный жизненный цикл убирает большинство случайных падений в серии запусков — каждый тест начинается с предсказуемого чистого состояния.

Частые ошибки

  • Забывают про static для @BeforeAll/@AfterAll.
  • Слишком много логики в setup/teardown.
  • Неправильный порядок очистки ресурсов.

Дополнительные вопросы

  • Как работает @TestInstance(Lifecycle.PER_CLASS)?
  • Опыт с вложенными тестами и lifecycle?

Какие практические примеры использования lifecycle-аннотаций в JUnit 5?

Middle

Типичный сценарий: в @BeforeAll поднимаем общий ресурс, в @BeforeEach готовим данные для конкретного теста, в @AfterEach чистим состояние.

Для API-тестов это часто выглядит как создание тестовых данных перед тестом и удаление после него, чтобы тесты не влияли друг на друга.

Из практики: правильный жизненный цикл резко снижает нестабильные падения в серии запусков.

Частые ошибки

  • Отсутствие очистки в случае ошибок.
  • Зависимости между тестами через состояние.
  • Избыточные действия в setup/teardown.

Дополнительные вопросы

  • Как организовать параллельное выполнение?
  • Стратегии работы с тестовыми данными?

Как вы используете параметризованные тесты в JUnit 5?

Middle

Параметризованные тесты создаются аннотацией @ParameterizedTest. Источники данных: @ValueSource (набор примитивов), @CsvSource (строки с несколькими значениями), @MethodSource (метод возвращает Stream), @EnumSource (все значения enum).

Повторяемые тесты — @RepeatedTest(n) — для нестабильных сценариев, но это не замена исправлению причины нестабильности.

Из практики: @CsvSource удобен для быстрого покрытия граничных значений прямо в коде теста, @MethodSource — когда данные сложные или читаются из файла.

Частые ошибки

  • Сложные @MethodSource без документации.
  • Избыточная параметризация простых тестов.

Дополнительные вопросы

  • Как организуете large test datasets?
  • Опыт с custom parameter resolvers?

Как вы применяете Maven: структура проекта, lifecycle и pom.xml?

Middle

Maven lifecycle состоит из фаз: validate → compile → test → package → verify → install → deploy. Каждая фаза включает предыдущие: `mvn test` выполнит validate, compile и test.

pom.xml содержит: groupId/artifactId/version (идентификатор), dependencies (зависимости), build/plugins (плагины), profiles (конфигурации для разных окружений). Структура проекта: src/main/java, src/test/java, src/test/resources.

Из практики: `mvn clean test -Dtest=LoginTest` — запуск одного теста без сборки всего; `mvn verify -P integration-tests` — интеграционные тесты через профиль. Знание команд экономит время при отладке CI.

Частые ошибки

  • Dependency version conflicts (version hell).
  • Неправильные dependency scopes.

Дополнительные вопросы

  • Как решаете dependency conflicts?
  • Опыт с custom lifecycle bindings?

Как вы применяете Maven для тестирования: plugins, profiles, CI/CD?

Middle

Maven для тестирования подключается через плагины: surefire запускает unit-тесты, failsafe — интеграционные, jacoco собирает покрытие.

Профили (profiles) позволяют запускать разные наборы тестов для разных окружений: mvn test -P integration, mvn test -P smoke.

Из практики: в CI передаю параметры через -D (mvn test -Denv=staging); сам failsafe отрабатывает на фазах integration-test и verify (verify проваливает сборку при упавших тестах), а старт и остановку окружения вешаю на pre-/post-integration-test отдельными плагинами (docker-maven-plugin, exec).

Частые ошибки

  • Медленный build из-за избыточных тестов.
  • Отсутствие test categorization.

Дополнительные вопросы

  • Как организуете test suites?
  • Опыт с custom Maven plugins?

Как вы применяете Rest Assured: фильтры и их применение?

Middle

Фильтр — это реализация интерфейса Filter: в методе filter() я перехватываю каждый запрос/ответ, чтобы централизованно логировать, собирать метрики или добавлять заголовки/авторизацию.

Готовые RequestLoggingFilter и ResponseLoggingFilter (или AllureRestAssured для отчётов) подключаю через given().filter(...) либо глобально RestAssured.filters(...).

Из практики: свой фильтр логирует тело запроса/ответа только при падении — причина видна сразу, без дублирования технического кода в каждом тесте.

Частые ошибки

  • Избыточное логирование в проде.
  • Неправильный порядок фильтров.

Дополнительные вопросы

  • Как реализовать retry механизм?
  • Опыт с custom error handling?

За что вы выбираете Selenide вместо Selenium?

Middle

Selenide упрощает интерфейсную автоматизацию за счёт удобных ожиданий и более лаконичного кода.

Я выбираю его, когда важно быстрее писать и поддерживать стабильные UI-проверки.

Из практики: меньше ручной работы с ожиданиями — меньше случайных падений.

Частые ошибки

  • Использование Thread.sleep вместо Selenide waits.
  • Игнорирование Configuration настроек в CI/CD.

Дополнительные вопросы

  • Когда предпочитаете pure Selenium?
  • Опыт с custom ElementsCollection matchers?

Как вы строите локаторы и действия с элементами в Selenide?

Middle

Локаторы строю через компактный $-синтаксис: $("#id") или $(By.cssSelector(...)) для одного элемента, $$(".row") — для коллекции, $x("//...") — для XPath.

Опираюсь на стабильные атрибуты и бизнес-смысл (data-testid, byText, byTagAndText), а не на хрупкие абсолютные XPath.

Действия пишу цепочкой на найденном элементе: $(...).click(), .setValue(...), .selectOption(...) — короткие читаемые шаги с ленивым поиском и авто-ожиданием.

Частые ошибки

  • Сложные XPath вместо CSS селекторов.
  • Отсутствие проверок перед действиями.

Дополнительные вопросы

  • Как работаете с Shadow DOM?
  • Опыт с custom selectors?

Как вы настраиваете ожидания и проверки в Selenide?

Middle

Ожидания в Selenide встроены в проверки: $(...).shouldBe(visible), shouldHave(text("...")), shouldNotBe(enabled) сами ждут выполнения условия, поэтому Thread.sleep не нужен.

Условия беру из класса Condition (visible, exist, text, value, enabled/clickable) — они описывают ожидаемое состояние элемента.

Глобальные параметры настраиваю через Configuration.timeout и Configuration.pollingInterval, а не жёсткие паузы — это главный фактор стабильности UI-регрессии в CI.

Частые ошибки

  • Избыточные явные ожидания.
  • Неиспользование collection operations.

Дополнительные вопросы

  • Как реализуете custom conditions?
  • Опыт с SoftAsserts в Selenide?

Как вы пишете API-тесты на Java с Rest Assured?

Middle

Строю тест в DSL Rest Assured: given() — baseURI, заголовки, тело и авторизация; when() — HTTP-метод и путь; then() — проверки.

В then() проверяю statusCode() и тело через body(...) с matchers (Hamcrest/JSONPath); нужные значения достаю через extract() для следующих шагов.

Базовые настройки и повторяющиеся проверки выношу в RequestSpecification/ResponseSpecification и отдельные методы для читаемости.

Из практики: такой формат помогает быстро локализовать поломку после изменений API.

Частые ошибки

  • Хардкод URLs и данных в тестах.
  • Игнорирование response time validation.

Дополнительные вопросы

  • Как организуете test данные management?
  • Опыт с JSON Schema validation?

Что важно знать QA про многопоточность в Java?

Senior

Ключевые механизмы Java: synchronized, volatile, happens-before и пакет java.util.concurrent (атомики, ConcurrentHashMap, ExecutorService) — понимаю, чем защищён общий стейт.

Целенаправленно проверяю гонки (race condition), взаимные блокировки (deadlock), livelock и утечки/исчерпание потоков в пулах.

Инструменты: стресс-прогоны с повторяемыми сценариями, thread dump / jstack и детекторы гонок для воспроизведения плавающих багов.

Из практики: такие дефекты редко ловятся с первого раза, поэтому нужны многократные прогоны, подробные журналы и контроль порядка выполнения.

Частые ошибки

  • Избыточная синхронизация (performance impact).
  • Неправильное использование volatile.

Дополнительные вопросы

  • Как избежать deadlock?
  • Когда использовать CompletableFuture?

Как вы используете extensions в JUnit 5?

Senior

Расширения JUnit 5 реализуют интерфейсы жизненного цикла: `BeforeEachCallback`, `AfterEachCallback` — до/после каждого теста; `TestInstancePostProcessor` — инициализация тестового объекта; `ParameterResolver` — внедрение параметров.

Регистрация через `@ExtendWith(MyExtension.class)` на классе или через `ServiceLoader` для глобальной регистрации. Популярный пример: `@ExtendWith(MockitoExtension.class)`, `@ExtendWith(SpringExtension.class)`.

Из практики: создаю собственный extension для логирования времени выполнения каждого теста через `BeforeEachCallback` + `AfterEachCallback` — помогает находить медленные тесты без сторонних инструментов.

Частые ошибки

  • Слишком много extensions замедляют тесты.
  • Неправильный порядок extensions.

Дополнительные вопросы

  • Как реализуете custom extensions?
  • Опыт с parallel test execution?

Какие продвинутые возможности Selenide вы используете на практике?

Senior

Продвинутые возможности: кастомные условия через `Condition.and()` и собственные реализации `Condition`; работа с коллекциями через `$$.filter(visible).shouldHave(size(3))`; `SelenideElement.as('имя')` для читаемых ошибок.

Работа с iframe: `switchTo().frame()`, JavaScript execution: `Selenide.executeJavaScript()`, `WebDriverRunner.getWebDriver()` для прямого доступа к WebDriver, автоскриншоты через `Configuration.screenshots=true`.

Из практики: `CollectionCondition.texts('A', 'B', 'C')` — элегантная проверка порядка элементов в списке вместо перебора в цикле. Значительно сокращает код тестов на проверках динамических списков.

Частые ошибки

  • Неоптимальная стратегия page load.
  • Отсутствие cleanup в teardown.

Дополнительные вопросы

  • Как масштабируете Selenide tests?
  • Опыт с remote WebDriver setup?

Чем интерфейс отличается от абстрактного класса в Java?

Junior

Интерфейс описывает контракт без состояния (до Java 8 — только сигнатуры; начиная с 8 — default- и static-методы). Класс реализует несколько интерфейсов.

Абстрактный класс может иметь поля, конструкторы, реализованные методы. От него наследуются одним классом.

Из практики: интерфейсы для горизонтальных способностей (Comparable, Serializable). Абстрактный класс — когда есть общая база с состоянием и поведением.

Частые ошибки

  • Делать «жирный» интерфейс с десятком методов вместо composition.
  • Считать default-методы заменой абстрактного класса полностью.

Дополнительные вопросы

  • Что такое sealed классы и интерфейсы?
  • Можно ли в интерфейсе иметь private методы?

Что такое equals и hashCode и какой их контракт?

Middle

equals определяет логическое равенство. hashCode возвращает int для использования в hash-структурах (HashMap, HashSet).

Контракт: если a.equals(b), то a.hashCode() == b.hashCode(); обратное не обязательно. Если переопределили equals — обязательно переопределяйте hashCode.

Из практики: IDE генерирует оба автоматически. В record-классах они генерируются языком. Нарушение контракта ведёт к багам, которые сложно отловить.

Частые ошибки

  • Переопределить equals и забыть hashCode — объект «теряется» в HashMap.
  • Использовать в hashCode mutable поля — после изменения объект пропадёт из множества.

Дополнительные вопросы

  • Что такое запись (record) в Java?
  • Чем Objects.equals удобен?

Что такое generics в Java и зачем они?

Middle

Generics — параметризованные типы: List<String>, Map<K, V>. Дают типобезопасность на этапе компиляции — нельзя положить Integer в List<String>.

Type erasure — после компиляции параметры стираются, в рантайме List<String> и List<Integer> неразличимы.

Из практики: используют ровно как в других языках, но помня про erasure — нельзя new T(), нельзя instanceof T.

Частые ошибки

  • Использовать raw type (List вместо List<...>) — теряется типобезопасность.
  • Игнорировать warning о unchecked operation.

Дополнительные вопросы

  • Что такое wildcard ?, extends, super?
  • Что такое PECS?

Что такое Optional и когда его использовать?

Middle

Optional<T> — обёртка, явно сигнализирующая «значение может отсутствовать». Методы: isPresent, get, orElse, orElseGet, map, ifPresent.

Используется как возвращаемый тип методов, которые могут не дать результат: Optional<User> findById(id).

Из практики: Optional не для полей объектов (увеличивает память, проблемы с сериализацией) и не для параметров. Только для возвращаемых типов методов.

Частые ошибки

  • Использовать Optional как поле — анти-паттерн.
  • Звать .get() без isPresent — выкинет NoSuchElementException.

Дополнительные вопросы

  • Чем orElse отличается от orElseGet?
  • Можно ли иметь Optional<Optional<T>>?

Что такое Stream API?

Middle

Stream API — функциональный способ обработки коллекций. Цепочка операций: filter, map, sorted, limit, collect. Терминальная операция запускает поток.

Streams ленивые — операции выполняются только при terminal operation. Промежуточные строят описание пайплайна.

Из практики: для функциональных преобразований, агрегаций, параллельной обработки (parallelStream). Не заменяет цикл, когда нужны побочные эффекты или сложная логика.

Частые ошибки

  • Использовать parallelStream без понимания — может быть медленнее.
  • Делать побочные эффекты в map — нарушает чистоту.

Дополнительные вопросы

  • Чем Stream.of отличается от Collection.stream?
  • Когда parallelStream даёт выгоду?

Что такое try-with-resources?

Junior

Конструкция try (Resource r = new Resource()) {...} автоматически закроет r после блока (вызовом close()). Работает с любым AutoCloseable.

Сравнима с with в Python — гарантирует освобождение ресурса даже при исключении. Можно объявить несколько ресурсов через ;.

Из практики: всегда используйте для файлов, соединений, потоков. Это резко снижает шанс утечек ресурсов.

Частые ошибки

  • Открывать ресурсы вне try-with-resources и забывать закрывать.
  • Полагаться, что close выкинет понятное исключение — оно может быть suppressed.

Дополнительные вопросы

  • Что такое suppressed exception?
  • Можно ли использовать уже инициализированный ресурс?

Что такое checked и unchecked исключения?

Junior

Checked — наследники Exception (не RuntimeException). Компилятор требует обрабатывать или declare через throws.

Unchecked — RuntimeException и потомки. Не требуют объявления, обычно сигнализируют о баге программы (NullPointerException, IllegalArgumentException).

Из практики: checked удобны для recoverable-ситуаций (IOException, SQLException). Unchecked — для программных ошибок. Тренд — больше unchecked: они меньше засоряют сигнатуры.

Частые ошибки

  • Ловить Exception целиком — глушит и unchecked, и checked.
  • Бросать RuntimeException там, где явно ожидается recovery.

Дополнительные вопросы

  • Чем Error отличается от Exception?
  • Что такое try/catch/finally?

Что такое volatile и synchronized?

Middle

volatile у поля гарантирует видимость изменений другими потоками и упорядоченность (happens-before). Без него поток может видеть «свою» копию значения.

synchronized — блок или метод, защищённый монитором объекта. В каждый момент только один поток внутри. Также даёт видимость памяти.

Из практики: volatile подходит для флагов и счётчиков с атомарным присваиванием. synchronized — для составных операций. Современный код часто использует Atomic-классы и Lock из java.util.concurrent.

Частые ошибки

  • Считать volatile достаточным для инкремента (++) — не атомарно.
  • Использовать synchronized(this) в публичном классе — снаружи кто-то может синхронизироваться на том же объекте.

Дополнительные вопросы

  • Что такое happens-before?
  • Чем ReentrantLock лучше synchronized?

Что такое функциональный интерфейс и lambda?

Middle

Функциональный интерфейс — интерфейс с одним абстрактным методом (@FunctionalInterface). Лямбда — компактный синтаксис анонимной реализации: list.forEach(x -> System.out.println(x)).

Стандартная библиотека: Function, Consumer, Supplier, Predicate, BiFunction. Доступны в java.util.function.

Из практики: используют со Stream API, для коллбэков, для DSL. Method reference (System.out::println) — ещё более лаконичная форма.

Частые ошибки

  • Капчурить mutable переменные в замыкании — нужно final или effectively final.
  • Делать лямбду на 30 строк — лучше выделить в метод.

Дополнительные вопросы

  • Чем method reference удобен?
  • Что такое effectively final?

Что такое CompletableFuture?

Middle

CompletableFuture<T> — асинхронный результат с возможностью композиции: thenApply, thenCompose, thenCombine, exceptionally, allOf, anyOf.

Запускают через CompletableFuture.supplyAsync(...) — выполняется на ForkJoinPool.commonPool по умолчанию или на переданном Executor.

Из практики: позволяет писать неблокирующий код без callback hell. Хорошо подходит для параллельных вызовов нескольких сервисов.

Частые ошибки

  • Использовать common pool для тяжёлых блокирующих операций — заблокируете весь pool.
  • Игнорировать exceptionally — silently глотаются ошибки.

Дополнительные вопросы

  • Чем CompletableFuture лучше старого Future?
  • Что такое join() vs get()?

Чем ArrayList отличается от LinkedList?

Junior

ArrayList основан на массиве — O(1) индексный доступ, O(n) вставка/удаление в середину. LinkedList — двусвязный список — O(1) вставка/удаление, но O(n) индексный доступ.

На практике ArrayList почти всегда быстрее: компактнее в памяти, дружелюбен к кешу CPU.

Из практики: используйте ArrayList по умолчанию. LinkedList оправдан только когда вставка/удаление в начало — частая операция (как Deque).

Частые ошибки

  • Выбирать LinkedList «потому что вставки быстрые» без понимания кеша.
  • Использовать LinkedList с .get(i) в цикле — катастрофическая производительность.

Дополнительные вопросы

  • Что такое ArrayDeque и чем оно лучше LinkedList как Deque?
  • Какая сложность у add в ArrayList?

Чем HashMap отличается от LinkedHashMap и TreeMap?

Middle

HashMap — на хешах, O(1) операции, порядок не гарантирован. LinkedHashMap — хеш плюс linked list по порядку вставки (или access order). TreeMap — на red-black tree, O(log n), отсортирован по ключу.

TreeMap позволяет операции по диапазону (subMap, headMap, tailMap). LinkedHashMap часто используется как LRU-кеш.

Из практики: по умолчанию HashMap. Когда важен порядок — LinkedHashMap. Когда нужна сортировка/диапазон — TreeMap.

Частые ошибки

  • Класть mutable ключи в HashMap и менять их — поломка структуры.
  • Использовать null-ключ в TreeMap — NullPointerException.

Дополнительные вопросы

  • Что такое access-order LinkedHashMap?
  • Как реализовать LRU кеш?

Что такое record-класс?

Middle

record — это компактная форма для иммутабельных носителей данных. record User(String name, int age) {} автоматически даёт final-поля, конструктор, equals/hashCode, toString, аксессоры.

Аналог data class в Kotlin. Доступен с Java 14 (preview), стабилен с Java 16.

Из практики: для DTO, value-объектов — идеально. Уменьшает шаблонный код. Можно добавлять методы и compact-конструктор с валидацией.

Частые ошибки

  • Делать record для сущности с identity — равенство по значению вводит в заблуждение.
  • Считать record полной заменой class — есть ограничения (нельзя наследоваться).

Дополнительные вопросы

  • Что такое sealed класс?
  • Можно ли у record добавить методы?

Что такое sealed классы?

Middle

sealed class/interface ограничивает набор подтипов: указывают permits A, B, C. Только эти классы могут наследоваться. Доступно с Java 17.

Полезно для замкнутых иерархий: Result = Success | Error. Компилятор знает все варианты — pattern matching в switch покрывает их exhaustive.

Из практики: помогает выражать ADT (algebraic data types) в Java. Хорошо сочетается с record и pattern matching.

Частые ошибки

  • Считать, что sealed предотвращает изменения в runtime — он на уровне компиляции.
  • Делать sealed во вложенных пакетах без правильного импорта.

Дополнительные вопросы

  • Чем sealed отличается от final?
  • Что такое pattern matching for switch?

Что такое Garbage Collector в JVM и какие виды?

Middle

GC — автоматический сборщик мусора JVM. Освобождает память от объектов, на которые нет ссылок. В современной JVM есть несколько GC: Serial, Parallel, G1 (по умолчанию с Java 9), ZGC, Shenandoah.

Различаются стратегиями: пауза (stop-the-world) vs throughput vs latency. G1 — баланс. ZGC и Shenandoah — низкая пауза (<10мс) ценой пропускной способности.

Из практики: для большинства приложений хватает G1 по умолчанию. ZGC выбирают, когда критична latency (трейдинг, real-time). Настройка GC — это про размер heap, young/old generation и пороги.

Частые ошибки

  • Тюнить GC без бенчмарков и метрик — ставит хуже.
  • Считать, что GC решает все проблемы памяти.

Дополнительные вопросы

  • Что такое stop-the-world?
  • Когда выбирать ZGC?

Что такое Selenide и чем он отличается от чистого Selenium?

Junior

Selenide — это обёртка над Selenium для Java, ориентированная на тесты. Лаконичный синтаксис, авто-ожидания, удобные ассерты, скриншоты при падении из коробки.

Selenium предоставляет низкий уровень управления браузером. Selenide добавляет «удобный фасад» с предсказуемой стабильностью.

Из практики: для нового UI-проекта на Java часто выбирают Selenide. Selenium остаётся для кастомных сценариев и интеграций.

Частые ошибки

  • Думать, что Selenide заменяет Selenium — он использует его под капотом.
  • Игнорировать настройку Configuration.

Дополнительные вопросы

  • Что такое Configuration.timeout?
  • Когда Selenide не подходит?

Как работает $(...) в Selenide?

Junior

$ — это короткая запись для Selenide.$, возвращает ленивый SelenideElement. Он не ищется сразу, а в момент действия (click, sendKeys, shouldBe).

Поддерживает CSS и Selenide-расширения: $(byText("...")), $(byTitle("...")), $(byAttribute(...)). Также $$ для коллекций.

Из практики: благодаря лениой природе $-локаторы стабильнее — auto-wait срабатывает при действии. Не нужно явных WebDriverWait.

Частые ошибки

  • Сохранять SelenideElement и потом удивляться, что StaleElementReference: лучше пересоздавать.
  • Использовать сложные XPath, когда есть by-методы.

Дополнительные вопросы

  • Что такое byText и когда оно полезно?
  • Как использовать $$ с фильтрами?

Что такое условие should/shouldBe и зачем оно?

Junior

should/shouldBe/shouldHave — это ретраящиеся ассерты, ждущие выполнения условия в течение Configuration.timeout. $(element).shouldBe(visible), .shouldHave(text(...)).

Это и есть основной механизм авто-ожидания в Selenide. Аналог expect(locator).toBeVisible() в Playwright.

Из практики: пишите ассерты на должное состояние, а не fixed sleep. Selenide сам подождёт нужное и упадёт с понятной ошибкой, если не дождался.

Частые ошибки

  • Использовать Selenide-условия с обычным assertEquals — теряется ретрай.
  • Завышать timeout — тесты медленные.

Дополнительные вопросы

  • Чем shouldBe отличается от shouldHave?
  • Какие условия встроены?

Как Selenide делает скриншоты при падении?

Junior

По умолчанию Selenide сохраняет скриншот и HTML страницы в build/reports/tests при падении теста. Путь настраивается через Configuration.reportsFolder.

Для JUnit/TestNG есть готовые listener-ы, прикладывающие артефакты в отчёт (Allure).

Из практики: эта функция «бесплатно» закрывает 90% запросов «дайте картинку, что было на экране перед падением».

Частые ошибки

  • Не настроить Allure listener и иметь скриншоты «в стороне».
  • Хранить тысячи скриншотов локально без чистки.

Дополнительные вопросы

  • Чем Allure Selenide listener полезен?
  • Как сделать скриншот вручную?

Как настроить браузер и driver в Selenide?

Middle

Конфигурация через Configuration.browser (chrome, firefox, edge, ie), Configuration.headless, Configuration.timeout, Configuration.baseUrl, Configuration.browserSize.

Драйверы скачиваются автоматически через WebDriverManager. Для удалённого запуска — Configuration.remote = "http://selenium-grid:4444/wd/hub".

Из практики: настройку обычно делают через System.properties или application.properties. Это позволяет менять окружение без правки кода.

Частые ошибки

  • Хардкодить browser в коде — мешает запускать на разных средах.
  • Не настраивать timeout и удивляться flaky.

Дополнительные вопросы

  • Что такое WebDriverManager?
  • Как запустить Selenide в Docker?

Что такое page object в Selenide?

Middle

Класс на страницу, содержащий локаторы как поля и методы действий. Часто аннотируют поля @FindBy или объявляют через SelenideElement.

page = Selenide.page(LoginPage.class) инициализирует все поля как SelenideElement.

Из практики: помогает декомпозировать тесты и снизить дублирование. Не превращайте POM в анемичный набор геттеров — добавляйте методы action.

Частые ошибки

  • Прятать в page object ассерты — теряется явность теста.
  • Делать POM на каждый компонент — оверкилл.

Дополнительные вопросы

  • Чем @FindBy отличается от $-локатора?
  • Что такое component object?

Что такое soft assertions в Selenide?

Middle

Обычные shouldBe сразу падают при ошибке. Soft assertions собирают все ошибки и кидают одну итоговую — удобно, когда хочется проверить несколько вещей за один проход.

Включается через Configuration.assertionMode = SOFT и SelenideLogger.addListener.

Из практики: помогает в UI-тестах с множеством мелких проверок. Минус — длиннее лог, иногда сложнее найти первопричину.

Частые ошибки

  • Использовать soft assertions везде — теряется fail-fast.
  • Не настраивать SelenideLogger — soft не сработает.

Дополнительные вопросы

  • Чем soft assertions отличаются от @SoftAssertions в AssertJ?
  • Когда лучше hard, а когда soft?

Как протестировать загрузку и скачивание файла в Selenide?

Middle

Загрузка: $("input[type=file]").uploadFile(new File("path")). Selenide подставит файл через DOM, не открывая диалог.

Скачивание: File f = $("a.download").download(); — Selenide перехватит скачивание и вернёт сохранённый файл. Поддерживает несколько режимов: PROXY, FOLDER, HTTPGET.

Из практики: для скачивания через ссылки часто стабильнее HTTPGET — Selenide дёргает URL сам. PROXY-режим работает с любой кнопкой, но настраивается сложнее.

Частые ошибки

  • Не настраивать FileDownloadMode и удивляться, что download не работает.
  • Хранить тестовые файлы в неправильных путях.

Дополнительные вопросы

  • Что такое FileDownloadMode.PROXY?
  • Чем uploadFile отличается от sendKeys?

Что такое holdBrowserOpen и зачем оно?

Middle

Configuration.holdBrowserOpen = true оставляет браузер открытым после завершения теста. Полезно для отладки — посмотреть финальное состояние страницы.

Также есть Configuration.reopenBrowserOnFail для пересоздания драйвера при потере.

Из практики: holdBrowserOpen — только локально. В CI это превратит сборку в зомби с десятками открытых браузеров.

Частые ошибки

  • Закоммитить holdBrowserOpen = true в CI.
  • Полагаться, что страница не изменится после теста — она может догрузить асинхронно.

Дополнительные вопросы

  • Как настроить headless = false для отладки?
  • Чем reopenBrowserOnFail полезен?

Как Selenide работает с iframe?

Middle

Selenide.switchTo().frame("name") или Selenide.switchTo().frame($("iframe#x")) переключают контекст. После работы выходят через switchTo().defaultContent() или switchTo().parentFrame().

Все последующие $-локаторы работают внутри iframe до выхода.

Из практики: типовая ошибка — забыть выйти из iframe и удивляться, почему не находятся элементы за его пределами. Полезно обернуть в try/finally или вспомогательный метод inFrame(...).

Частые ошибки

  • Забывать switchTo().defaultContent().
  • Полагаться, что $(...) автоматически проникнет в iframe — нет.

Дополнительные вопросы

  • Как написать вспомогательный inFrame(action)?
  • Чем switchTo().frame отличается от parentFrame()?

Чем JUnit 5 отличается от JUnit 4?

Middle

JUnit 5 — модульная архитектура (Jupiter, Vintage, Platform). Современные возможности: @DisplayName, @Nested, @ParameterizedTest, расширения через Extension API, java.time, лямбды.

JUnit 4 — монолитный, аннотации проще (@Test, @Before, @After). Уже не развивается, поддерживается в legacy через Vintage.

Из практики: для нового проекта — JUnit 5. Миграция с 4 пошагово через Vintage Engine, который позволяет запускать оба одновременно.

Частые ошибки

  • Смешивать импорты org.junit и org.junit.jupiter.
  • Использовать @BeforeAll без static в JUnit 5 без @TestInstance(PER_CLASS).

Дополнительные вопросы

  • Что такое JUnit Platform?
  • Что даёт @Nested?

Какие основные аннотации JUnit 5?

Junior

@Test — это тестовый метод. @BeforeEach/@AfterEach — перед/после каждого теста. @BeforeAll/@AfterAll — один раз на класс (static или с PER_CLASS).

@DisplayName — человеко-читаемое имя. @Disabled — пропустить. @Tag — категоризация. @Timeout — лимит времени. @RepeatedTest, @ParameterizedTest — повтор и параметризация.

Из практики: для разных категорий тестов используют @Tag и фильтруют запуск (mvn test -Dgroups=smoke).

Частые ошибки

  • Использовать org.junit.Test (JUnit 4) вместо org.junit.jupiter.api.Test.
  • Полагаться, что @BeforeEach выполнится в порядке наследования — это так, но порядок методов в одном классе не гарантирован.

Дополнительные вопросы

  • Чем @RepeatedTest отличается от @ParameterizedTest?
  • Что делает @Order?

Что такое @ParameterizedTest и какие источники данных?

Middle

@ParameterizedTest запускает тест на разных входных данных. Источники: @ValueSource (примитивы и строки), @CsvSource, @CsvFileSource, @MethodSource (своя статическая фабрика), @EnumSource, @ArgumentsSource (полностью кастомный).

Имя теста настраивается шаблоном: @ParameterizedTest(name = "{0} → {1}").

Из практики: для проверки бизнес-логики на наборе кейсов — самый удобный способ. @CsvFileSource подходит, когда данных много или они приходят из тест-аналитика.

Частые ошибки

  • Класть в @CsvSource огромный объём — теряется читаемость.
  • Использовать @ValueSource там, где нужна более одной колонки.

Дополнительные вопросы

  • Что такое @MethodSource?
  • Чем @ArgumentsSource удобен?

Что такое Extension в JUnit 5?

Middle

Extension — механизм расширения JUnit 5 на смену Runner и Rule из JUnit 4. Регистрируется через @ExtendWith. Реализует callback-интерфейсы: BeforeAllCallback, AfterEachCallback, ParameterResolver, TestWatcher.

Применения: внедрить зависимости в параметры теста, обработать исключения, добавить логирование, временные ресурсы.

Из практики: библиотеки вроде Mockito (@ExtendWith(MockitoExtension.class)), SpringBoot (@SpringBootTest), Testcontainers — всё через Extension.

Частые ошибки

  • Применять @ExtendWith вручную там, где есть готовая мета-аннотация.
  • Делать тяжёлые BeforeAllCallback — замедление всех тестов класса.

Дополнительные вопросы

  • Что такое ParameterResolver?
  • Чем Extension лучше Rule/Runner?

Что такое @Nested и зачем оно?

Middle

@Nested — вложенные классы внутри теста, образующие иерархию. Помогает группировать тесты по контексту: @Nested class WhenLoggedIn { @Nested class AndAdmin {... }}.

Каждый уровень может иметь свои @BeforeEach и фикстуры — упрощает структуру shared-setup'ов.

Из практики: BDD-стиль — отчёты получаются как «when ... and ... then ...», понятно даже PM-у.

Частые ошибки

  • Создавать слишком глубокую вложенность — путаница.
  • Делать @Nested static — он должен быть inner class.

Дополнительные вопросы

  • Чем @Nested отличается от обычных классов?
  • Можно ли использовать @TestInstance(PER_CLASS) в @Nested?

Чем assertEquals отличается от assertThat?

Junior

assertEquals и assertTrue — встроенные ассерты JUnit. Просты, но при ошибке дают скудное сообщение.

assertThat — стиль из AssertJ или Hamcrest. assertThat(list).hasSize(3).contains("a"). Чейнинг, читаемые сообщения, тонкая семантика.

Из практики: для серьёзного проекта добавьте AssertJ. Сообщения об ошибках получаются информативные, тесты пишутся быстрее.

Частые ошибки

  • Смешивать assertThat из Hamcrest и AssertJ — несовместимые API.
  • Передавать lambda там, где нужно значение.

Дополнительные вопросы

  • Чем AssertJ лучше Hamcrest?
  • Как сделать кастомный assert?

Что такое @Timeout?

Middle

@Timeout задаёт максимальное время выполнения теста. Если превышено — fail. Доступно на тесте, классе и через assertTimeoutPreemptively.

Режимы threadMode: SAME_THREAD (в том же потоке), SEPARATE_THREAD (в отдельном потоке, можно прервать), INFERRED (по умолчанию — выводится из конфигурации).

Из практики: спасает от случайных deadlock'ов и зависаний — CI не висит часами. Но не лечит причину, только сигнализирует.

Частые ошибки

  • Ставить очень короткий timeout, без учёта CI-машины.
  • Использовать assertTimeoutPreemptively с операциями, которые нельзя прерывать.

Дополнительные вопросы

  • Чем @Timeout отличается от assertTimeout?
  • Что такое ThreadMode.SEPARATE_THREAD?

Что такое @Disabled и когда его применять?

Junior

@Disabled пропускает тест или класс. Лучше указывать reason: @Disabled("flaky, ticket #123") или @Disabled("awaiting feature toggle").

Также есть условные варианты: @DisabledIfEnvironmentVariable, @DisabledOnOs, @EnabledIfSystemProperty.

Из практики: правило — каждый @Disabled с причиной и сроком. Иначе превращается в долгое мёртвое поле в коде.

Частые ошибки

  • Использовать @Disabled без объяснения.
  • Накапливать disabled-тесты — теряется регрессионное покрытие.

Дополнительные вопросы

  • Чем @Disabled отличается от @Ignore из JUnit 4?
  • Что такое quarantine pattern?

Что такое @TestFactory?

Middle

@TestFactory создаёт «динамические тесты» — Collection/Stream/Iterator из DynamicTest объектов. Каждый DynamicTest имеет имя и Executable.

В отличие от @ParameterizedTest, набор тестов вычисляется в runtime — например, из внешнего файла, через API.

Из практики: подходит для генерируемых тестов (например, BDD-сценарии из feature-файла). Менее распространено, чем parameterized, но мощно.

Частые ошибки

  • Считать @TestFactory заменой parameterized — у них разные сценарии.
  • Делать слишком много динамических тестов — отчёт раздувается.

Дополнительные вопросы

  • Чем @TestFactory отличается от @ParameterizedTest?
  • Что такое DynamicContainer?

Какие лучшие практики наименования тестов?

Junior

Имя должно отвечать на вопросы: что тестируем, при каких условиях, какой результат. Примеры: shouldReturnEmptyList_whenNoOrders, login_withInvalidPassword_returns401.

Также популярна BDD-форма given_when_then. Для @DisplayName можно писать предложением: «Login fails with invalid password».

Из практики: хорошее имя — это документация. Когда тест падает, по имени сразу понимаешь, что сломалось, без чтения кода.

Частые ошибки

  • Имена вроде test1, testFoo — никакой информации.
  • Длинные имена с подробностями реализации — устаревают быстро.

Дополнительные вопросы

  • Что лучше: @DisplayName или говорящее имя метода?
  • Чем BDD-стиль удобен?

Чем String отличается от StringBuilder?

Junior

String иммутабельный — каждая операция (+, replace) создаёт новый объект. StringBuilder — изменяемый буфер, можно эффективно конкатенировать в цикле.

Для одной-двух конкатенаций — String. Для цикла или сложной сборки — StringBuilder.

Из практики: компилятор оптимизирует a + b + c в StringBuilder автоматически. Но в цикле всё равно вручную.

Частые ошибки

  • Использовать String в цикле — N^2 аллокаций.
  • Использовать StringBuffer без необходимости — он thread-safe, медленнее.

Дополнительные вопросы

  • Чем StringBuilder отличается от StringBuffer?
  • Что такое String pool?

Что такое immutable объект и как его создать?

Middle

Immutable объект нельзя изменить после создания. Все поля final, нет setter'ов, в конструкторе копируем mutable аргументы.

Класс final (или конструктор приватный) — чтобы нельзя было наследоваться и изменить поведение. Geter'ы возвращают копии mutable полей.

Из практики: immutable объекты автоматически thread-safe, легко кешируются, безопасны для использования как ключи Map. String, Integer — классические примеры.

Частые ошибки

  • Считать класс immutable, имея mutable поле без копирования.
  • Возвращать ссылку на внутренний массив или коллекцию.

Дополнительные вопросы

  • Что такое defensive copy?
  • Чем record упрощает создание immutable?

Что такое try-finally vs try-with-resources?

Middle

try-finally — старая форма: вручную закрывать ресурсы в finally. Многословно, легко забыть, проблема с suppressed exceptions.

try-with-resources автоматически закроет AutoCloseable после блока, в правильном порядке. Уменьшает шаблонный код, безопаснее.

Из практики: для нового кода всегда try-with-resources. try-finally остаётся для случаев, когда ресурс не реализует AutoCloseable.

Частые ошибки

  • Закрывать ресурс в finally без проверки на null — может бросить NPE.
  • Не использовать try-with-resources для AutoCloseable.

Дополнительные вопросы

  • Что такое suppressed exception?
  • Можно ли иметь несколько ресурсов в try-with-resources?

Что такое equals и hashCode для коллекций?

Middle

HashMap, HashSet используют hashCode для бакета и equals для проверки. Если переопределили equals и не hashCode — объекты теряются в коллекциях.

Контракт: a.equals(b) => a.hashCode() == b.hashCode(). Обратное не обязательно.

Из практики: используйте Objects.hash(field1, field2, ...) для быстрой реализации. IDE автогенерирует. record делает это автоматически.

Частые ошибки

  • Переопределить только equals.
  • Использовать mutable поля в hashCode — после изменения объект пропадает из множества.

Дополнительные вопросы

  • Что произойдёт, если положить mutable объект в HashSet и потом изменить его?
  • Чем record упрощает equals/hashCode?

Что такое Comparable и Comparator?

Middle

Comparable<T> — интерфейс с методом compareTo. Реализуют сами классы, чтобы определить «естественный порядок».

Comparator<T> — отдельный объект, передаваемый в сортировку: Comparator.comparing(User::getName). Позволяет иметь несколько вариантов сортировки.

Из практики: для одного-единственного порядка — Comparable. Для разных сортировок (по имени, по возрасту, обратном) — Comparator.

Частые ошибки

  • Делать compareTo несогласованным с equals — Set ведёт себя странно.
  • Возвращать огромные числа в compareTo вместо -1/0/1.

Дополнительные вопросы

  • Чем Comparator.reversed() полезен?
  • Как сравнивать null-значения?

Что такое ConcurrentHashMap и почему он быстрее synchronized HashMap?

Middle

ConcurrentHashMap разрешает параллельные чтения без блокировок, а запись блокирует точечно — на уровне отдельного бакета/узла, а не всей карты.

synchronized Map блокирует всю коллекцию на каждой операции — под нагрузкой это узкое место.

Из практики: до Java 7 была сегментная блокировка (Segment), с Java 8 — CAS + synchronized на голове бакета. По умолчанию для многопоточного map берут ConcurrentHashMap; compute/computeIfAbsent атомарны.

Частые ошибки

  • Использовать putIfAbsent + get вместо computeIfAbsent — два хода вместо одного атомарного.
  • Класть null в ConcurrentHashMap — NPE (в отличие от HashMap).

Дополнительные вопросы

  • Что такое computeIfAbsent?
  • Можно ли итерировать ConcurrentHashMap во время изменения?

Что такое ThreadLocal и для чего он?

Middle

ThreadLocal<T> — это контейнер, у которого свой набор значений для каждого потока. Помогает избежать передачи параметров через много слоёв (тип контекста, текущий пользователь).

Минусы: легко создать утечку памяти (особенно в thread pool, где потоки переживают запрос), сложно отлаживать.

Из практики: типичный сценарий — request context в web-фреймворке. Очищать в finally обязательно (иначе следующий запрос получит чужие данные).

Частые ошибки

  • Не очищать ThreadLocal в пуле потоков — утечка.
  • Использовать ThreadLocal вместо явной передачи параметра — неявность.

Дополнительные вопросы

  • Что такое InheritableThreadLocal?
  • Чем ScopedValue (Java 21) лучше?

Что такое volatile и happens-before?

Middle

volatile гарантирует видимость записи в поле другим потокам. Java Memory Model говорит: запись в volatile happens-before чтения того же volatile.

Без volatile процессор может закешировать значение, и другой поток будет видеть старое.

Из практики: volatile подходит для флагов (running, shutdown), счётчиков с одиночной записью. Для compound-операций (i++) — AtomicInteger.

Частые ошибки

  • Использовать volatile для i++ — не атомарно.
  • Полагать, что volatile делает блок кода атомарным.

Дополнительные вопросы

  • Что такое CAS?
  • Чем AtomicInteger лучше volatile int?

Что такое ExecutorService?

Middle

ExecutorService — абстракция над пулом потоков. Создают через Executors.newFixedThreadPool(N), submit(Callable) возвращает Future, awaitTermination ждёт завершения.

Подвиды: fixed, cached, scheduled, virtualThreadExecutor (Java 21). Каждый под свои задачи.

Из практики: не создавайте сырые Thread в production-коде. ExecutorService даёт контроль над жизненным циклом и пулом.

Частые ошибки

  • Использовать Executors.newCachedThreadPool без верхнего лимита — может создать тысячи потоков.
  • Забыть shutdown() — JVM не остановится.

Дополнительные вопросы

  • Чем submit отличается от execute?
  • Что такое Virtual Threads?

Что такое Spring Bean и Dependency Injection?

Middle

Bean — это объект, управляемый Spring контейнером. Spring создаёт, инициализирует, инжектит зависимости и управляет жизненным циклом.

DI — внедрение зависимостей через конструктор, поле или setter. Это позволяет писать слабо связанный код и легко подменять реализации в тестах.

Из практики: предпочтительно injection через конструктор — явные зависимости, легко тестировать. Поле/setter — для специальных случаев.

Частые ошибки

  • Использовать field injection везде — сложно тестировать вне Spring.
  • Создавать слишком много beans без оправдания.

Дополнительные вопросы

  • Чем @Component отличается от @Service?
  • Что такое scope бина?

Что такое Maven и Gradle и когда что выбирать?

Middle

Maven — XML-конфигурация, conventions-driven, более предсказуемый. Gradle — DSL на Groovy/Kotlin, гибче, быстрее на инкрементальной сборке.

У обоих своя экосистема. Maven дольше существует, поэтому больше документации и плагинов.

Из практики: для серверных Java-проектов часто Maven (стабильность и понятность). Для Android и сложных мультимодульных — Gradle.

Частые ошибки

  • Смешивать Maven и Gradle в одном проекте без понимания.
  • Не использовать dependency-locking.

Дополнительные вопросы

  • Что такое dependency convergence?
  • Чем Gradle wrapper полезен?

Что такое аннотации и где их используют?

Middle

Аннотации — метаданные на классе, поле, методе. Бывают source (для компилятора), runtime (доступны через reflection).

Применяются для: фреймворков (Spring @Component, JUnit @Test), валидации (@NotNull), документации (@Deprecated), сериализации (@JsonProperty).

Из практики: своих аннотаций обычно не делают — пользуются готовыми. Если делать — нужно понимание Retention, Target и reflection.

Частые ошибки

  • Использовать сложную аннотацию-магию без понимания.
  • Не учитывать, что reflection медленнее обычного кода.

Дополнительные вопросы

  • Что такое @Retention(RetentionPolicy.RUNTIME)?
  • Как написать свою аннотацию?

Что такое serialization и какие подходы есть?

Middle

Стандартная Java-сериализация (Serializable) — встроенная, но медленная и небезопасная (риск выполнения произвольного кода при десериализации).

Альтернативы: JSON (Jackson, Gson), Protocol Buffers, MessagePack. JSON — самый ходовой для HTTP API.

Из практики: для нового кода — JSON через Jackson. Serializable оставляют для специфичных случаев (RMI); из JDK его не удаляют, но считают ошибкой дизайна — поэтому есть фильтры десериализации (JEP 290) и долгосрочные планы заменить механизм.

Частые ошибки

  • Использовать ObjectInputStream с недоверенными данными — RCE.
  • Полагаться на Serializable для долговременного хранения.

Дополнительные вопросы

  • Что такое serialVersionUID?
  • Чем Jackson быстрее Gson?

Что такое pattern matching в современной Java?

Middle

С Java 17+ доступен pattern matching для instanceof: if (obj instanceof String s) { use s } — приведение и присваивание в одном.

Pattern matching for switch (Java 21) — мощнее: switch по типам, deconstruction record, охрана условиями.

Из практики: радикально снижает шаблонный код. Особенно ценно при работе с sealed классами и record.

Частые ошибки

  • Использовать старый instanceof + cast в новом коде.
  • Не учитывать exhaustiveness — switch требует покрытия всех вариантов sealed.

Дополнительные вопросы

  • Что такое record deconstruction?
  • Чем switch expression удобнее обычного?

Что такое Virtual Threads (Project Loom)?

Middle

Виртуальные потоки (Java 21) — лёгкие потоки, не привязанные 1:1 к OS-потокам. Можно запустить миллион виртуальных потоков, JVM мапит их на маленький пул carriers.

Хорошо подходят для IO-bound: блокирующий вызов внутри Virtual Thread не блокирует carrier — он перехватывает.

Из практики: для серверов, обрабатывающих тысячи соединений, virtual threads — революция. Не нужна асинхронность через CompletableFuture, можно писать обычный синхронный код.

Частые ошибки

  • Использовать virtual threads для CPU-bound — не помогут, нужно ограничение количества.
  • Pinning на synchronized — virtual thread «зависает» с carrier.

Дополнительные вопросы

  • Что такое pinning?
  • Чем virtual threads отличаются от Kotlin coroutines?

Что такое Selenide.open и как настроить базовый URL?

Junior

Selenide.open("/login") открывает страницу. baseUrl задаётся в Configuration.baseUrl или через System.property selenide.baseUrl.

Можно открыть конкретный класс page object: open("/login", LoginPage.class) — возвращает инициализированный объект.

Из практики: для разных окружений (dev/stage) baseUrl выносят в System.property, что позволяет переключаться без сборки.

Частые ошибки

  • Хардкодить полный URL в коде.
  • Не очищать состояние между тестами — cookies сохраняются.

Дополнительные вопросы

  • Как переключиться на другую среду в CI?
  • Что такое Selenide.clearBrowserCookies?

Чем sleep() и Selenide.sleep() отличаются от обычного Thread.sleep?

Middle

Selenide.sleep — обёртка над Thread.sleep с понятным API. Но любая sleep — анти-паттерн в тестах, потому что зависит от среды.

Лучше: используйте should/shouldBe с auto-wait. Если действительно нужно ждать — ждите по условию через Selenide.Wait().until.

Из практики: каждое использование sleep в тестах — потенциальный flaky. Перед merge-ом всё sleep пересматривают.

Частые ошибки

  • Использовать sleep везде «на всякий случай».
  • Поднимать timeout вместо причины замедления.

Дополнительные вопросы

  • Что такое Selenide.Wait()?
  • Чем should отличается от sleep + assertions?

Что такое CollectionCondition и как его использовать?

Middle

$$ возвращает ElementsCollection. Для него ассерты — CollectionCondition: size(N), texts("a", "b"), containExactTextsCaseSensitive, etc.

Пример: $$("li.row").shouldHave(size(5)). Auto-wait такой же, как у одиночных.

Из практики: незаменимо для проверки списков, таблиц, повторяющихся компонентов.

Частые ошибки

  • Использовать .size() и assertEquals — теряется retry.
  • Перебирать $$ в цикле с index и забыть про auto-wait.

Дополнительные вопросы

  • Что такое filter() для $$?
  • Как проверить, что в списке есть конкретный элемент?

Что такое selectOptionByText vs selectOptionByValue?

Junior

Для <select> Selenide предоставляет: selectOption(int index), selectOptionByValue(String), selectOptionContainingText(String), selectOption(String text).

Выбор зависит от стабильности: value стабильнее текста (особенно при локализации).

Из практики: для production-тестов предпочтительно selectOptionByValue. Если переводы стабильны и тестируется один язык — можно по тексту.

Частые ошибки

  • Зависеть от текста при тестировании нескольких локалей.
  • Использовать индекс — порядок может измениться.

Дополнительные вопросы

  • Как тестировать кастомный dropdown (не native select)?
  • Что такое selectRadio?

Что такое executeJavaScript в Selenide?

Middle

Selenide.executeJavaScript("return arguments[0].textContent", element) — выполняет JS в браузере. Возвращает результат.

Применяют для: чтения свойств, недоступных через WebDriver API; кастомных манипуляций с DOM; ускорения подготовки данных.

Из практики: использовать осторожно — JS-манипуляции далеки от пользовательского сценария. Лучше через UI, если возможно.

Частые ошибки

  • Делать через JS то, что можно через обычные действия.
  • Не учитывать различия движков (Chrome vs Firefox).

Дополнительные вопросы

  • Чем executeAsyncJavaScript отличается?
  • Когда JS обязателен?

Что такое addBaseUrl и navigator?

Middle

В публичном API Selenide методов addBaseUrl и navigator нет. Базовый URL задаётся через Configuration.baseUrl (или системным свойством/переменной окружения).

Selenide.open(path) открывает Configuration.baseUrl + path; если нужен абсолютный адрес — передают полный URL.

Навигация — статические методы Selenide.back(), forward(), refresh(); внутренний класс Navigator публично не используется.

Из практики: обычно достаточно open и переходов по UI; back/forward/refresh — для сценариев вроде Cancel в форме или восстановления после reload.

Частые ошибки

  • Использовать back() для отмены формы — лучше через UI.
  • Refresh посреди теста — теряется состояние.

Дополнительные вопросы

  • Как тестировать поведение при refresh?
  • Что такое Selenide.switchTo()?

Что такое browser webdriver listeners в Selenide?

Middle

SelenideLogger.addListener подключает свои обработчики событий теста: до/после действия. Так работает Allure listener — он перехватывает действия Selenide и формирует шаги.

Также можно сделать кастомные listener для метрик, отчётов, скриншотов на каждом шаге.

Из практики: Allure listener — стандарт. Подключают в @BeforeAll или один раз на проект.

Частые ошибки

  • Не подключать Allure и не получить шаги в отчёте.
  • Делать тяжёлые операции в listener — замедляются все тесты.

Дополнительные вопросы

  • Что такое EventListener в Selenium?
  • Чем AllureSelenide удобен?

Что такое downloadFolder в Selenide?

Middle

Configuration.downloadsFolder — путь для скачиваемых файлов. При FOLDER mode Selenide следит за этой папкой.

Также можно ввести Configuration.fileDownload = FOLDER / PROXY / HTTPGET — режимы скачивания.

Из практики: FOLDER подходит, когда нет сложной авторизации и достаточно мониторить папку. PROXY — для проксирования всех запросов и перехвата загрузок.

Частые ошибки

  • Не очищать downloadsFolder перед тестом — старые файлы могут спутать тест.
  • Использовать FOLDER в headless-Chrome без специальной настройки CDP.

Дополнительные вопросы

  • Что такое PROXY mode?
  • Чем CDP-настройка downloads?

Чем Selenide отличается от Selenium FluentWait?

Middle

В чистом Selenium для ожидания нужно явно создавать WebDriverWait и условие. В Selenide auto-wait встроен в каждое действие и каждый shouldBe.

Это устраняет 90% boilerplate WebDriverWait и делает тесты короче.

Из практики: можно по прежнему использовать FluentWait, если auto-wait недостаточно гибок (например, для нетривиальных условий).

Частые ошибки

  • Смешивать Selenide auto-wait и явный WebDriverWait — поведение усложняется.
  • Завышать Configuration.timeout вместо разбора причины.

Дополнительные вопросы

  • Когда auto-wait не подходит?
  • Что такое Selenide.Wait().until?

Какие подводные камни Selenide в headless-режиме?

Middle

В headless Chrome могут не работать: скачивание файлов (нужен CDP), некоторые анимации, особые pop-up. Screenshot работают.

Расширения, авторизация через сертификат, биометрия — не поддерживаются.

Из практики: для CI обычно достаточно headless. Для отладки сложных багов запускают в обычном режиме (Configuration.headless = false).

Частые ошибки

  • Запускать сложные сценарии (drag-drop с файлами) только в headless и удивляться, что в проде иначе.
  • Не обновлять chromedriver под версию браузера.

Дополнительные вопросы

  • Что такое new headless mode в Chrome?
  • Какие фичи отличаются между обычным и headless?

Что такое аннотации JPA и какие популярные?

Middle

JPA — Java Persistence API. Аннотации: @Entity (класс — таблица), @Id (PK), @GeneratedValue, @Column, @OneToMany/ManyToOne, @JoinColumn, @Transient (не сохранять).

Hibernate — самая известная реализация JPA. Spring Data JPA даёт удобный repository-слой поверх.

Из практики: JPA удобна, но создаёт абстракцию над SQL — иногда генерирует неоптимальные запросы. Знание SQL под капотом критично.

Частые ошибки

  • Полагать, что JPA сама всё оптимизирует.
  • Использовать @OneToMany без понимания N+1.

Дополнительные вопросы

  • Что такое lazy vs eager loading?
  • Чем JOIN FETCH помогает?

Что такое Spring Boot и зачем он?

Middle

Spring Boot — convention-over-configuration поверх Spring. Auto-configuration, starters (spring-boot-starter-web и т.д.), встроенный сервер (Tomcat/Jetty), production-ready (metrics, health checks).

Один application.properties + main class — запускается полноценное приложение.

Из практики: де-факто стандарт для Java backend. Уменьшает шаблонный код в разы по сравнению с чистым Spring.

Частые ошибки

  • Считать Spring Boot 'магией' без понимания auto-configuration.
  • Подключать лишние starter — раздувание зависимостей.

Дополнительные вопросы

  • Что такое Spring Boot Actuator?
  • Чем native image (GraalVM) полезен?

Что такое REST Controller в Spring?

Middle

@RestController = @Controller + @ResponseBody. Методы автоматически возвращают JSON. Маршруты через @GetMapping, @PostMapping и т.д.

Параметры: @PathVariable, @RequestParam, @RequestBody, @RequestHeader.

Из практики: для REST API в Spring почти всегда @RestController. Возвращайте ResponseEntity для контроля над кодом и заголовками.

Частые ошибки

  • Возвращать сырые объекты без обёртки в DTO.
  • Не обрабатывать ошибки через @ControllerAdvice.

Дополнительные вопросы

  • Что такое @ControllerAdvice?
  • Чем @ResponseStatus полезен?

Что такое транзакционность @Transactional в Spring?

Middle

@Transactional оборачивает метод в БД-транзакцию. propagation определяет поведение при вложенных вызовах: REQUIRED (по умолчанию — переиспользовать или создать), REQUIRES_NEW (всегда новая), SUPPORTS, NEVER.

Откат: по умолчанию на unchecked exception. rollbackFor = Exception.class — откат на любых.

Из практики: типовая ошибка — вызывать @Transactional метод внутри того же класса — proxy не сработает, транзакция не создастся.

Частые ошибки

  • Вызывать @Transactional метод изнутри того же бина.
  • Не настраивать rollback для checked exception.

Дополнительные вопросы

  • Что такое propagation?
  • Чем self-invocation problem решается?

Что такое Bean lifecycle в Spring?

Middle

Жизненный цикл бина: instantiation → populate properties → BeanNameAware, BeanFactoryAware → BeanPostProcessor (before) → @PostConstruct / InitializingBean.afterPropertiesSet() → BeanPostProcessor (after) → ready. При остановке: @PreDestroy → DisposableBean.destroy().

Можно подключаться к этапам через aware-интерфейсы и аннотации.

Из практики: для большинства задач хватает @PostConstruct для инициализации и @PreDestroy для очистки.

Частые ошибки

  • Делать тяжёлую работу в конструкторе.
  • Не закрывать ресурсы в @PreDestroy.

Дополнительные вопросы

  • Что такое scope бина?
  • Чем BeanPostProcessor полезен?

Что такое Mockito и как мокать зависимости?

Middle

Mockito — самая популярная библиотека для мокирования в Java. Создаёт фейковые объекты, чьё поведение можно настроить: when(mock.method()).thenReturn(value).

Также: verify(mock).method() — проверка вызова, argument captor — захват аргументов.

Из практики: моки помогают изолировать тестируемый класс от его зависимостей. Не моките value-объекты — это бесполезно.

Частые ошибки

  • Моки на final методы без mockito-inline.
  • Перемокать всё и тестировать моки, а не код.

Дополнительные вопросы

  • Что такое spy?
  • Чем @MockBean отличается от @Mock?

Что такое AssertJ и зачем?

Middle

Библиотека fluent assertions: assertThat(list).hasSize(3).contains("a").doesNotContain("x"). Цепочка проверок, читаемый код, понятные сообщения при ошибке.

Заменяет JUnit assertEquals/assertTrue для более выразительных проверок.

Из практики: для нового кода обычно подключают AssertJ. assertJ-core достаточен для большинства проверок.

Частые ошибки

  • Смешивать AssertJ и Hamcrest — путаница.
  • Использовать сырой assertEquals для сложных объектов.

Дополнительные вопросы

  • Чем AssertJ лучше Hamcrest?
  • Что такое soft assertions в AssertJ?

Что такое Testcontainers?

Middle

Библиотека для запуска Docker-контейнеров из Java-тестов. Запускает БД, брокеры, кеши — реальные, не моки.

Каждый тест может получить чистую инстанцию. После теста контейнер удаляется.

Из практики: радикально улучшает качество интеграционных тестов. Минус — медленнее юнит-тестов, поэтому отдельный профиль.

Частые ошибки

  • Использовать testcontainers в unit-тестах — медленно.
  • Не закрывать контейнеры — утечка.

Дополнительные вопросы

  • Что такое Singleton container pattern?
  • Чем testcontainers лучше моков?

Что такое RestAssured?

Middle

DSL для тестирования REST API в Java. given().contentType(JSON).body(req).when().post("/users").then().statusCode(201).body("id", notNullValue()).

Удобный синтаксис для send-validate. Поддерживает JSON Schema validation, OAuth, авторизацию.

Из практики: для функциональных тестов API в Java — стандарт. Альтернатива — HttpClient + ObjectMapper, но многословнее.

Частые ошибки

  • Использовать для производительности — RestAssured медленнее raw HTTP.
  • Полагать, что RestAssured заменит Postman.

Дополнительные вопросы

  • Чем JsonPath в RestAssured удобен?
  • Как тестировать контракты с RestAssured?

Что такое Java Records и Pattern Matching вместе?

Middle

record User(String name, int age) {} плюс switch (obj) { case User(String name, int age) -> ... } — компактные ADT в Java.

Pattern matching for switch (Java 21) даёт деструктуризацию record и exhaustive проверку для sealed.

Из практики: современный код выглядит почти как Kotlin/Scala. Намного выразительнее старых патентов.

Частые ошибки

  • Использовать record для бизнес-сущности с identity.
  • Не помнить, что record не может наследоваться.

Дополнительные вопросы

  • Чем record отличается от data class?
  • Что такое sealed + record + pattern matching ADT?

Что такое Optional vs null и когда что?

Middle

Optional<T> — обёртка, явно выражающая «значение может отсутствовать». Используется как возвращаемый тип методов.

Не для полей, не для параметров. Внутри полей и в простых случаях — допустимы null.

Из практики: Optional делает API безопаснее, но не превращайте всё в Optional — это создаёт лишний шум.

Частые ошибки

  • Использовать Optional как поле — анти-паттерн.
  • Звать .get() без isPresent.

Дополнительные вопросы

  • Что такое orElseThrow?
  • Чем Optional.flatMap отличается от map?

Что такое Java Streams Collectors?

Middle

Collectors — фабрики для terminal operation .collect(). toList(), toMap(), groupingBy, partitioningBy, joining.

С Java 16+ — toList() есть прямо на Stream. С Java 10 — toUnmodifiableList(). С 16 — toList() возвращает unmodifiable.

Из практики: groupingBy и partitioningBy — самые полезные для отчётности и группировок.

Частые ошибки

  • Использовать старый Collectors.toList() везде — теперь есть проще.
  • Не учитывать mutability коллекций.

Дополнительные вопросы

  • Чем groupingBy отличается от partitioningBy?
  • Что такое downstream collector?

Что такое Stream API parallelStream и подводные камни?

Middle

parallelStream использует ForkJoinPool.commonPool для параллельной обработки. Не всегда быстрее sequential!

Подводные камни: общий pool на JVM, не подходит для блокирующих операций, требует thread-safe код, разрушение порядка.

Из практики: profile сначала, parallelStream только если измерения показали выгоду. Для CPU-bound с большими данными — может ускорить.

Частые ошибки

  • Использовать parallelStream для маленьких коллекций.
  • Делать побочные эффекты в параллельном потоке.

Дополнительные вопросы

  • Что такое ForkJoinPool common pool?
  • Когда лучше CompletableFuture, чем parallelStream?

Что такое immutable collections в Java?

Middle

List.of(1, 2, 3), Set.of(...), Map.of(k, v) — immutable коллекции (Java 9+). Попытка модификации — UnsupportedOperationException.

Также Collections.unmodifiableList(list) — обёртка над существующим (но оригинал можно изменить).

Из практики: для возвращаемых из методов значений — immutable. Это безопаснее (никто не изменит), но создаёт overhead аллокации.

Частые ошибки

  • Возвращать mutable коллекцию и удивляться, что снаружи её меняют.
  • Считать Collections.unmodifiableList настоящей immutable — обёртка над mutable.

Дополнительные вопросы

  • Что такое copy-on-write?
  • Чем List.copyOf отличается от unmodifiableList?

Что такое exception chain в Java?

Middle

При перехвате одного исключения и бросании другого, оригинал можно прикрепить как cause: throw new ServiceException("failed", originalException).

В stack trace отображается «Caused by:» — видна вся цепочка.

Из практики: при wrapping исключения всегда передавайте cause — иначе теряется диагностическая информация. Логи становятся бесполезными.

Частые ошибки

  • Бросать новое исключение без передачи cause.
  • Делать слишком длинные wrapping-цепочки без необходимости.

Дополнительные вопросы

  • Что такое suppressed exception в try-with-resources?
  • Как читать стектрейс?