Java


Гео и язык канала: Россия, Русский
Категория: Технологии



Гео и язык канала
Россия, Русский
Категория
Технологии
Статистика

Spring Boot 4 убрал статус WILL_EXPIRE_SOON: проверьте мониторинг сертификатов

В Spring Boot 3 SSL-компонент health endpoint переходил в особый статус WILL_EXPIRE_SOON, если сертификат скоро истекал. Для проб Kubernetes и балансировщиков это было неудобно: непонятно, считать сервис живым или нет.

В Spring Boot 4 этот статус убрали. Health остаётся UP, а цепочки сертификатов, срок которых скоро закончится, перечисляются в details.expiringChains. Порог предупреждения задаётся свойством management.health.ssl.certificate-validity-warning-threshold:
management:
  health:
    ssl:
      certificate-validity-warning-threshold: 14d
  endpoint:
    health:
      show-details: always
Ответ /actuator/health тогда выглядит так:
{
  "status": "UP",
  "details": {
    "expiringChains": ["server-cert…"]
  }
}
Практический вывод простой: пробы больше не будут дёргаться из-за истекающего сертификата, но и алерт по смене статуса тоже не сработает. Если мониторинг ловил WILL_EXPIRE_SOON, переключите его на проверку непустого expiringChains и не забудьте включить show-details, иначе деталей в ответе не будет.


💡 Java: переопределяешь equals() - переопредели и hashCode()
Два объекта с одинаковым email могут быть равны по equals(), но HashSet.contains() при этом вернёт false.
Причина: HashSet и HashMap сначала используют хеш, чтобы определить, где искать объект, а затем проверяют равенство. Если хеши различаются, до equals() дело может не дойти.
Правило: равные объекты обязаны иметь одинаковый hashCode(). При этом одинаковый хеш не гарантирует равенство - коллизии допустимы.
В примере на картинке равенство определяется по email, поэтому хеш тоже вычисляется по нему.
И ещё: не меняйте поля, участвующие в equals() и hashCode(), пока объект хранится в HashSet или используется как ключ HashMap - поиск может перестать его находить.
#Java #OOP


💡 Java: не используй `Thread.sleep()` для ожидания работы

Thread.sleep() - это просто пауза на заданное время.

Проблема в том, что ты угадываешь:

❌ поток может проснуться слишком рано  
❌ или наоборот, ждать дольше, чем нужно  
❌ polling через sleep() создаёт лишние задержки и проверки

Для координации потоков лучше использовать:

✅ wait() / notify()  
✅ CountDownLatch  
✅ другие примитивы синхронизации из java.util.concurrent

Например:
CountDownLatch done = new CountDownLatch(1);

done.await();      // ждём сигнал
done.countDown();  // работа завершена
А если задача должна выполняться периодически, вместо бесконечного цикла с sleep() лучше взять
ScheduledExecutorService exec =
    Executors.newSingleThreadScheduledExecutor();

exec.scheduleAtFixedRate(
    this::doWork,
    0,
    1,
    TimeUnit.SECONDS
);

Thread.sleep() хорошо подходит для простой задержки.
Для ожидания события и периодических задач в Java есть более точные инструменты.

#Java #Concurrency


типов из других модулей

В Spring Boot 4 появилась полезная штука для конфигурации больших проектов.

@ConfigurationPropertiesSource позволяет генерировать metadata для типов, которые находятся не в текущем модуле, а, например, в общей библиотеке или отдельном starter-модуле.

Раньше с такими внешними типами IDE-подсказки для application.yml и application.properties
могли быть неполными или вообще отсутствовать.

Теперь можно явно указать источник конфигурационных свойств и получить нормальную metadata даже для внешних классов.

Особенно полезно для:

— multi-module проектов
— внутренних Spring Boot starter'ов
— shared configuration libraries
— больших платформенных команд

Мелкая фича, но для DX в крупных Spring Boot проектах очень приятная.

#SpringBoot4 #Config


💡 **Когда `ReentrantReadWriteLock` быстрее обычного `synchronized`**

Если приложение часто читает данные и редко их изменяет, один эксклюзивный lock может стать узким местом.

`ReentrantReadWriteLock` разделяет блокировки:

- несколько потоков могут одновременно держать `readLock`;
- `writeLock` получает исключительный доступ;
- во время записи блокируются и читатели, и другие писатели.

```java
private final ReentrantReadWriteLock lock =
new ReentrantReadWriteLock();

private final Lock readLock = lock.readLock();
private final Lock writeLock = lock.writeLock();

public String get(String key) {
readLock.lock();
try {
return cache.get(key);
} finally {
readLock.unlock();
}
}

public void put(String key, String value) {
writeLock.lock();
try {
cache.put(key, value);
} finally {
writeLock.unlock();
}
}
```

Такой подход полезен для кешей, справочников и конфигураций, которые постоянно читаются, но обновляются редко.

Однако `ReentrantReadWriteLock` подходит не всегда. При коротких операциях, высокой конкуренции между писателями или частых обновлениях накладные расходы могут оказаться выше, чем у обычного `synchronized` или `ReentrantLock`.

Используйте его для read-heavy сценариев и обязательно проверяйте результат под реальной нагрузкой.


💡 Не используйте `Stream.peek()` для бизнес-логики

peek() предназначен в первую очередь для отладки: он выполняет побочное действие внутри цепочки и зависит от терминальной операции.
users.stream
()
    .peek(System.out::println)
    .toList();
Для реальной обработки выбирайте:

* map() — преобразовать элементы;
* filter() — отфильтровать;
* forEach() — явно выполнить действие.

Код со скрытыми побочными эффектами сложнее читать, тестировать и поддерживать.

#Java #Streams


Видео недоступно для предпросмотра
Смотреть в Max
10 дистрибутивов Linux - 10 боевых стилей. Пейн разбирает, кому что подойдёт: от Ubuntu новичкам до Arch для тех, кто любит контроль. Выбор зависит от задачи, а не от хайпа

#linux #ubuntu #arch #наруто


🚀 HTTP-клиенты в Spring Boot через @HttpExchange

Описываете внешний API обычным Java-интерфейсом:

@HttpExchange("https://api.example.com";)
public interface UserClient {

@GetExchange("/users/{id}")
User findById(@PathVariable Long id);

@PostExchange("/users")
User create(@RequestBody User user);
}

Spring сам создаёт реализацию клиента, после чего его можно внедрять как обычный bean.

Таймауты, SSL и другие параметры настраиваются через spring.http.clients.*.

Меньше шаблонного кода, понятный контракт и удобное тестирование.

#SpringBoot4 #Java #HttpExchange


🔥 Хочешь быстрее расти в IT? Хватит учиться в одиночку

В IT прокачивается тот, кто каждый день видит сильные идеи, новые инструменты, реальные задачи, вакансии и разборы.

Окружение решает больше, чем кажется.

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

AI: t.me/ai_machinelearning_big_data
Python: t.me/pythonl
Linux: t.me/linuxacademiya
Хакинг: t.me/linuxkalii
DevOps: t.me/DevOPSitsec
Docker: t.me/DevopsDocker
Golang: t.me/Golang_google
Rust: t.me/rust_code
C++: t.me/cpluspluc
C#: t.me/csharp_1001_notes
Java: t.me/java_library
JavaScript: t.me/javascriptv
React: t.me/react_tg
Frontend: t.me/front
PHP: t.me/phpshka
Android: t.me/android_its
Мобильная разработка: t.me/mobdevelop
Базы данных: t.me/sqlhub
Data Science: t.me/data_analysis_ml
Big Data: t.me/bigdatai
Математика: t.me/data_math
Физика: t.me/fizmat
Kubernetes: t.me/kubernetc
GameDev: https://t.me/gamedev
Haskell: t.me/haskell_tg

Собеседования и карь
ера:

DS собеседования: t.me/machinelearning_interview
Python собеседования: t.me/python_job_interview

Папка с вакансиями: t.me/addlist/_zyy_jQ_QUsyM2Vi
Папка Go разработчика: t.me/addlist/MUtJEeJSxeY2YTFi
Папка Python разработчика: t.me/addlist/eEPya-HF6mkxMGIy
Папка ML: https://t.me/addlist/2Ls-snqEeytkMDgy
Папка Frontend: https://t.me/addlist/mzMMG3RPZhY2M2Iy

Полезное
сверху:

ИТ-мемы: t.me/memes_prog
Английский для программистов: t.me/english_forprogrammers
ИИ и технологии: t.me/vistehno
954 ГБ open-source курсов: @courses
ИТ-книги бесплатно: https://t.me/addlist/BkskQciUW_FhNjEy

Max Ai:
https://max.ru/ai_machinelearning_big_data
Max python: https://max.ru/pythonl
ТЕХНО: https://max.ru/vistehno
Max Go: https://max.ru/Golang_google
 
Max Linux: https://max.ru/linuxkalii
 
Devops:
https://max.ru/DevOPSitsec
C#: https://max.ru/csharp_ci
C++: https://max.ru/cpluspluc
SQL: https://max.ru/sqlhub
Java: https://max.ru/javatg

Подписывайся на нужные направления и собирай себе ленту, которая реально двигает вперёд.

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


💡 Java: тестируйте не только happy path

Баги часто прячутся не в «обычных» данных, а на краях:

✅ `null` может внезапно дать `NullPointerException`

✅ пустая коллекция может сломать логику агрегации

✅ один элемент часто ведёт себя иначе, чем список из 10 значений

Один тест на `List.of(1, 2, 3)` проверяет только красивый сценарий.

Добавьте проверки на `null`, `empty` и boundary values — и код сразу станет заметно надёжнее.

#Java #Testing #JUnit


Google снова поделилась внутренней магией.
Компания выложила Copybara — инструмент, который годами помогал ей синхронизировать код между разными репозиториями.
Допустим у вас есть закрытый корпоративный репозиторий и публичная open-source версия. Copybara помогает держать их в актуальном состоянии без ручного копирования, костылей и бесконечных cherry-pick’ов.
По сути, это инструмент для тех, кто живёт между private и public repo и не хочет превращать синхронизацию кода в отдельную боль.
https://github.com/google/copybara


В России можно посещать IT-мероприятия хоть каждый день: как оффлайн, так и онлайн

Но где их находить? Как узнавать о них раньше, чем когда все начнут выкладывать фотографии оттуда?


Переходите на канал IT-Мероприятия России. В нём каждый день анонсируются мероприятия со всех городов России

📆 в канале размещаются как онлайн, так и оффлайн мероприятия;
👩‍💻 можно найти ивенты по любому стеку: программирование, frontend-backend разработка, кибербезопасность, дата-аналитика, osint, devops и другие;
🎙 разнообразные форматы мероприятий: митапы с коллегами по цеху, конференции и вебинары с известными опытными специалистами, форумы и олимпиады от важных представителей индустрии и многое другое

А чтобы не искать по разным форумам и чатам новости о предстоящих ивентах:

🚀 IT-мероприятия России — подписывайся и будь в курсе всех предстоящих мероприятий!


🖥 В Java длинные цепочки вроде `user → address → city` легко превращаются в ловушку для `NullPointerException`.
Обычный вариант быстро разрастается:
User user = userRepository.findByEmail(email);

if (user != null) {
    Address address = user.getAddress();
    if (address != null) {
        return address.getCity();
    }
}
return "unknown";
Проблема не только в количестве строк.

В таких вложенных if легко забыть один уровень проверки и получить NPE в самом неожиданном месте.
С Optional это можно записать короче и безопаснее:
return userRepository.findByEmail(email)
    .map(User::getAddress)
    .map(Address::getCity)
    .orElse("unknown");
Каждый map() выполняется только если предыдущее значение существует.
Если пользователя нет, адреса нет или город не задан - цепочка спокойно дойдёт до orElse().
Для таких случаев Optional хорошо работает как способ явно показать:
значение может отсутствовать, и это нормальная часть логики.

Главное не превращать Optional в новую религию.
Он особенно полезен на границах методов и в цепочках, где каждый следующий шаг может вернуть null.


В Java для enum лучше часто брать не HashSet, а EnumSet.

EnumSet - это специальная реализация Set, заточенная именно под enum-значения.
Фишка в том, что внутри он хранит элементы не как обычные объекты в hash-таблице, а как битовую маску.
Каждое значение enum получает свой бит:
enum Permission {
    READ,
    WRITE,
    DELETE
}
Если в наборе есть READ и WRITE, внутри это может быть просто несколько включённых битов.
Из-за этого операции вроде contains, add, remove, union, intersection работают очень быстро и занимают меньше памяти.

Пример:
EnumSet admin = EnumSet.of(
    PermissionD.REA,
    Permission.WRITE,
    Permission.DELETE
);

boolean canWrite = admin.contains(Permission.WRITE);
Для прав доступа, статусов, флагов, режимов и feature toggles это почти идеальный вариант.
HashSet тоже будет работать, но чаще это лишняя тяжесть.

Если набор состоит только из enum-значений - сначала подумайте про EnumSet.


Java: никогда не доверяйте внешнему вводу напрямую

Пользовательский ввод нельзя сразу подставлять в SQL, путь к файлу или бизнес-логику.

Плохой вариант:
String sql = "SELECT * FROM users WHERE email = '" + email + "'";
Files.readString(Path.of("/data/" + filename));
int age = Integer.parseInt(request.getParameter("age"));
На вид обычный код, но в нём сразу несколько проблем:
• SQL-инъекции;
• path traversal через ../;
• некорректные типы и диапазоны;
• мусорные данные, которые ломают логику дальше по системе.
Правильный подход - валидировать данные на границе приложения и использовать безопасные API.
if (!EMAIL.matcher(email).matches()) {
    throw new BadRequestException("invalid email");
}
PreparedStatement ps = conn.prepareStatement(
    "SELECT * FROM users WHERE email = ?"
);
ps.setString(1, email);
Для файлов - нормализовать путь и проверять, что пользователь не вышел за разрешённую директорию:
Path base = Path.of("/data").toAbsolutePath().normalize();
Path file = base.resolve(filename).normalize();
if (!file.startsWith(base)) {
    throw new SecurityException("path escape blocked");
}
А для DTO лучше сразу описывать правила:
public record SignupRequest(
    @NotBlank @Email String email,
    @Min(18) @Max(120) int age
) {}
Чем раньше вы отсекаете плохие данные, тем меньше шансов, что они превратятся в уязвимость внутри системы.


💡 Java: вложенные if-else быстро превращают код в лабиринт

Когда вся логика уходит в глубокую вложенность, читать метод становится больно: основной сценарий спрятан где-то внизу, а перед ним несколько уровней проверок.
Проще использовать guard clauses - ранние проверки с выходом из метода.
Было:
- проверяем user
- внутри проверяем active
- внутри проверяем order
- внутри проверяем paid
- только потом выполняем основное действие
Стало:
- если user == null - сразу ошибка
- если user неактивен - сразу ошибка
- если order == null - сразу ошибка
- если order не оплачен - сразу ошибка
- основная логика остаётся плоской и читаемой

Такой код легче:
- читать
- тестировать
- ревьюить
- расширять без превращения метода в пирамиду из if

Guard clauses - маленький приём, который сильно улучшает чистоту кода.


Spring Boot: уберите try/catch из контроллеров
В Spring Boot не нужно размазывать обработку ошибок по каждому endpoint через бесконечные try/catch.
Для этого есть @RestControllerAdvice.
Идея простая: вы выносите обработку исключений в один глобальный класс, а контроллеры оставляете чистыми.

Например:
@RestControllerAdvice
public class GlobalExceptionHandler {
    @ExceptionHandler(ResourceNotFoundException.class)
    public ResponseEntity handleNotFound(ResourceNotFoundException ex) {
        return ResponseEntity
                .status(HttpStatus.NOT_FOUND)
                .body(new ErrorResponse("NOT_FOUND", ex.getMessage()));
    }
    @ExceptionHandler(IllegalArgumentException.class)
    public ResponseEntity handleBadRequest(IllegalArgumentException ex) {
        return ResponseEntity
                .badRequest()
                .body(new ErrorResponse("BAD_REQUEST", ex.getMessage()));
    }
    @ExceptionHandler(Exception.class)
    public ResponseEntity handleGeneric(Exception ex) {
        return ResponseEntity
                .status(HttpStatus.INTERNAL_SERVER_ERROR)
                .body(new ErrorResponse("INTERNAL_ERROR", "Something went wrong"));
    }
}
После этого контроллер может выглядеть спокойно:
@GetMapping("/users/{id}")
public User getUser(@PathVariable Long id) {
    return userService.findById(id);
}
Если пользователь не найден - сервис кидает ResourceNotFoundException, а Spring сам отправит нормальный 404.

Что это даёт:
• меньше мусора в контроллерах
• единый формат ошибок
• проще поддерживать API
• легче логировать исключения
• меньше копипасты в endpoint-ах

Контроллер должен описывать сценарий запроса, а не превращаться в свалку обработки ошибок.


🚀 Java - regex без боли

Нашли интересную библиотеку - Sift. Она заменяет весь этот ад с регулярками на нормальный fluent API.
Теперь вместо нечитаемых строк вида:
^[0-9a-fA-F]{6}$
Пишешь код, который реально понимаешь:
.char('#')
.then()
.exactly(6)
.hexDigits()
📌 Что это дает:
- Читаемый и понятный код
- Меньше ошибок в regex
- Быстрее разработка и поддержка
По сути — это как “переводчик” с языка регулярных выражений на человеческий Java-код.
Если когда-нибудь ломал голову над regex -это прям must-have.
#Java #JavaDev


⚡️ CORS в Spring Boot: не лечите это костылями на фронте
Если frontend и backend живут на разных доменах или портах, браузер начнет резать запросы по CORS. Это не баг Spring Boot и не проблема React. Это нормальный механизм безопасности браузера.
Правильный способ - настроить CORS на стороне backend.
В Spring Boot это можно сделать глобально через WebMvcConfigurer: указать маршруты, разрешенные origins, HTTP-методы, заголовки и работу с credentials.
Главное - не ставить бездумно * везде подряд, особенно если используете cookies, токены или allowCredentials(true). В проде лучше явно перечислять доверенные домены, например frontend-домен приложения.
Такой подход дает централизованный контроль: вы один раз задаете политику CORS и не размазываете настройки по каждому контроллеру.
Для Java backend-разработчика это базовая, но важная вещь: CORS должен быть частью архитектуры API, а не случайной правкой перед деплоем.


⚡️ Перестаём писать методы с 7+ параметрами
Если сигнатура выглядит как:
createUser(firstName, lastName, email, phone, address, city, country)
Это уже сигнал, что модель данных развалилась.
Проблема не только в читаемости.  
Такие методы сложнее поддерживать, расширять и тестировать. Любое изменение ломает сигнатуру и тянет за собой каскад правок.
Нормальный вариант - собрать связанные данные в объект:
UserInfo userInfo
Получаем:
- чище API  
- проще добавлять поля  
- меньше ошибок при передаче параметров  
- код начинает отражать доменную модель, а не список строк  
Это базовый приём, но именно на нём чаще всего экономят, а потом платят сложностью.

Показано 20 последних публикаций.