📚 Лайфхаки, приёмы и лучшие практики для Java-разработчиков. Всё, что ускорит код и прокачает навыки. Java, Spring, Maven, Hibernate. По всем вопросам @evgenycarter РКН clck.ru/3KoGeP
🧠 Коллекторы и toList()
в Java 16+: можно ли заменить collect(Collectors.toList())
на просто .toList()
?
Да, но с нюансами.
📌 Короткий ответ:
Если ты используешь Java 16+, можешь заменить:
List<String> list = stream.collect(Collectors.toList());
List<String> list = stream.toList();
.toList()
возвращает немодифицируемый список (immutable).Collectors.toList()
возвращает modifiable ArrayList.
var list1 = List.of("a", "b");
var list2 = list1.stream().toList();
list2.add("c"); // 💥 UnsupportedOperationException
var list3 = list1.stream().collect(Collectors.toList());
list3.add("c"); // ✅ OK
Collectors.toList()
— это ArrayList (или его сабкласс)..toList()
— это неопределённый тип внутри JDK (часто List.of
под капотом).
if (list instanceof ArrayList) ...
.toList()
оптимизирован для параллельных стримов: может работать быстрее, но также может повлиять на порядок, если ты этого явно не контролируешь.ArrayList
).Заменяйcollect(Collectors.toList())
на.toList()
, только если тебе действительно не нужен изменяемый список. Это безопасно при соблюдении условий, но может привести к неожиданным багам в тестах и проде, если забыть про неизменяемость.
🧠 Record (Java 16+) + pattern matching для instanceof
(Java 14+) в Java 17+ позволяют писать лаконичный и безопасный код:
📌 Запись DTO с валидацией через компактный конструктор:
public record User(String name, String email) {
public User {
Objects.requireNonNull(name, "name не должен быть null");
if (!email.contains("@")) {
throw new IllegalArgumentException("Неверный email: " + email);
}
}
}
toString()
, equals()
, hashCode()
без лишнего кода.
Object obj = …;
if (obj instanceof User u) {
System.out.println("Привет, " + u.name());
}
public record Team(String name, List<String> members) {
public Team {
members = List.copyOf(members);
}
}
members
нельзя изменить извне.public record X(List<String> list)
без копирования — рискуете нарушить неизменяемость!👩💻 JPQL: как писать запросы, которые не сломают Hibernate
Узнайте, как писать JPQL-запросы, которые ускорят Hibernate в 5 раз, избегая критических ошибок, тормозящих 80% проектов!
Приглашаем на открытый урок
🗓 19 июня в 20:00 МСК
🆓 Бесплатно. Урок в рамках старта курса «Java Developer. Professional».
🎯 О чём поговорим:
✔️- JPQL vs SQL: почему ваши запросы ломают Hibernate и как их переписать так, чтобы БД не «умирала» под нагрузкой.
✔️ Тайные ловушки: антипаттерны JPQL, генерирующие N+1 SELECT и тормозящие приложение, и методы их поиска в коде.
✔️ Оптимизация на максимум: как использовать JOIN FETCH, подзапросы и кэширование в JPQL для мгновенного ускорения Hibernate.
👥 Кому будет интересно:
Java-разработчикам, использующим Hibernate, системным архитекторам и инженерам по оптимизации производительности.
💡В результате урока вы:
Научитесь писать эффективные JPQL-запросы, избегать распространённых ошибок и значительно ускорять работу Hibernate-приложений.
🎁 Дарим промокод, который дает скидку на обучение - JAVA_06
🔗 Ссылка на регистрацию: https://vk.cc/cMKvog
Реклама. ООО «Отус онлайн-образование», ОГРН 1177746618576
⁉️ Монолит или микросервисы? Руководство для архитекторов, которые ценят свои нервы
Приглашаем на открытый урок.
🗓 17 июня в 20:00 МСК
🆓 Бесплатно. Урок в рамках старта курса «Software Architect».
📌 Что будет на вебинаре:
✔️ Как не попасть в ловушку “модных” микросервисов;
✔️ Разбор признаков, что пора выходить из монолита;
✔️ Архитектурные паттерны для перехода к микросервисам (Strangler Fig, BFF, Self-contained systems);
✔️ Организационные и технические риски — что точно пойдёт не так и как это предсказать;
✔️ Роль DevOps, CI/CD и мониторинга в выборе архитектуры.
👥 Для кого этот вебинар:
- Разработчиков Backend и FullStack, участвующих в архитектурных решениях;
- Архитекторов ПО, которые планируют масштабирование приложений;
- Тимлидов и DevOps-инженеров, выстраивающих процесс разработки и доставки;
- Технических менеджеров, выбирающих стратегию развития продукта.
🎯 После вебинара вы:
- Получите пошаговое руководство по выбору архитектуры под ваш проект;
- Научитесь оценивать реальные риски и стоимость микросервисов;
- Поймёте, как внедрять архитектурные изменения без сбоев и хаоса;
- Увидите, как принимать взвешенные архитектурные решения, сохраняя технический контроль и производительность команды.
💡 Идеальный вебинар для тех, кто хочет перестать "архитектурить на ощущениях" и начать действовать стратегически.
🎁 Всем участникам вебинара дарим промокод, который дает скидку на обучение - SoftwareArc_06
👉 Регистрация на вебинар: https://vk.cc/cMHSzz
Реклама. ООО «Отус онлайн-образование», ОГРН 1177746618576
⚡️ Быстрые альтернативы HashMap: EnumMap, массивы и примитивные коллекции
🧠 EnumMap вместо HashMap (enum-ключи)
📌 EnumMap<K, V>
хранит данные в массиве → нет хеширования и boxing’а.
EnumMap<Status, String> map = new EnumMap<>(Status.class);
map.put(Status.STARTED, "Запущен");
String status = map.get(Status.STARTED);
[0…MAX]
и известны заранее.
int MAX = 1000;
var cache = new String[MAX + 1];
cache[42] = "ответ";
String result = cache[42];
var fastMap = new it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap<String>();
fastMap.put(42, "ответ");
String res = fastMap.get(42);
switch
, а не Map.
String handle(int code) {
return switch (code) {
case 100 -> "OK";
case 200 -> "Created";
default -> "Other";
};
}
switch
в tableswitch
или lookupswitch
— молниеносно.🚀 Spring WebFlux с Server-Sent Events 🚀
Улучшите свои приложения в режиме реального времени с помощью #SpringWebFlux и Server-Sent Events! 🔥
👉@BookJava
🎯 Java-хаки: динамический вывод с помощью printf()
Статья посвящена методу printf() в Java, который используется для создания форматированной строки вывода. В ней рассматривается синтаксис метода, различные спецификаторы формата (например, %d, %f, %s и т.д.) и то, как с их помощью управлять отображением чисел, строк и других типов данных в консоли.
Приведены примеры использования System.out.printf()
с пояснениями по флагам, ширинe и точности форматирования, а также показано, как легко создавать динамический и читаемый вывод в приложениях на Java.
https://springframework.guru/java-output-printf-method/
👉@BookJava
Как масштабировать машинные модели и работать с огромными объемами данных? Откройте для себя возможности Spark ML на открытом уроке от OTUS!
Spark ML — это мощный инструмент для масштабируемого машинного обучения, который позволяет обучать модели на больших данных, не переходя на специализированные ML-системы. Мы покажем, как интеграция с Spark SQL и DataFrame API упрощает ETL-подготовку данных и фичуризацию для реальных проектов.
Убедитесь, как Spark ML решает задачи отказоустойчивости и распределённых вычислений, позволяя вам легко строить промышленные ML-пайплайны.
Посетите открытый урок 11 июня в 20:00 МСК в преддверие старта курса «Spark Developer» и получите скидку на обучение: https://vk.cc/cMyLJ3
Реклама. ООО «Отус онлайн-образование», ОГРН 1177746618576
👩💻 Java — один из самых востребованных языков, но не каждый разработчик умеет использовать его возможности по максимуму.
На курсе «Java Developer. Professional» вы научитесь создавать современные Java-приложения, освоите Spring WebFlux и Kafka, а также разберётесь в работе JVM изнутри.
Пройдите тест, проверьте, достаточно ли у вас знаний для обучения на курсе:.
🎁 Дарим промокод, который дает скидку на обучение - JAVA_06
На курсе вас ждёт практическая работа с кодом, детальные разборы, ревью от экспертов и подходы, позволяющие писать эффективный и чистый код.
Начните свой путь к уровню Middle+ и используйте Java на 100%.
➡️ Пройти вступительный тест курса: https://vk.cc/cMxZwr
Реклама. ООО «Отус онлайн-образование», ОГРН 1177746618576
Optional.stream()
появился в Java 9 и позволяет превратить Optional<T>
в Stream<T>
длины 0 или 1. Зачем это нужно?
📌 Когда применять?
◾️ 🧠 Интеграция с цепочками Stream API: если у вас есть коллекция Optional<T>
, можно собрать все непустые значения без дополнительных проверок:
List<Optional<User>> userOptionals = …;
List<User> users = userOptionals.stream()
.flatMap(Optional::stream) // из каждого Optional либо 1 элемент, либо пусто
.collect(Collectors.toList());
Optional.stream()
пришлось бы делать что-то вроде filter(Optional::isPresent).map(Optional::get)
.Optional<Something>
и вы хотите «сливать», а не оставлять пустые обёртки.Если вы строите конвейер обработки данных, а на каком-то шаге может не быть значения — Optional.stream()
поможет аккуратно пропустить «пустышки» и не ломать последующие операции.
Optional
, — использование stream()
создаёт впечатление, что у вас реально коллекция элементов, хотя всего лишь 1 или 0. Для простых случаев ifPresent()
, map()
, orElse()
читается понятнее.
// Менее канонично:
optionalValue.stream().forEach(v -> doSomething(v));
// Лучше:
optionalValue.ifPresent(v -> doSomething(v));
Optional.stream()
создаёт объект стрима и небольшую внутреннюю структуру, что на горячем участке кода (в tight loop) может сказаться на производительности. Если вместо него можно обойтись map().orElse()
, задумайтесь о легковесном варианте.Optional.stream()
в одиночном случае (не в контексте объединения множества опционалов), код может стать менее очевидным. Например:
// Что тут происходит?
Stream.of(opt1, opt2, opt3)
.flatMap(Optional::stream)
.findFirst();
Optional<User> result = opt1.isPresent() ? opt1
: opt2.isPresent() ? opt2
: opt3;
flatMap
и filter
без промежуточных Optional
.
List<Order> orders = getOrders();
// Для каждого заказа пытаемся получить пользователя из БД,
// но он может быть не найден (Optional<User>).
List<Optional<User>> maybeUsers = orders.stream()
.map(o -> userRepository.findById(o.getUserId()))
.toList();
// Теперь формируем список уже «существующих» юзеров:
List<User> users = maybeUsers.stream()
.flatMap(Optional::stream)
.collect(Collectors.toList());
Optional.stream()
полностью оправдан: сразу избавляемся от «пустых» опционалов.Не используйте Optional.stream()
внутри метода, который ожидает ровно одно значение или бросает исключение, если опционал пуст.
// Плохо:
User user = optionalUser.stream()
.findFirst()
.orElseThrow(() -> new NotFoundException("User not found"));
// Лучше так:
User user = optionalUser
.orElseThrow(() -> new NotFoundException("User not found"));
Optional.stream()
только когда действительно нужно объединить несколько Optional-ов в один Stream и пропустить пустые.🧠 Конфигурация Spring Boot 3 через record
и @ConstructorBinding
Вместо традиционных @Data
+ пустого конструктора можно сразу использовать Java 17 record
для настройки свойств:
📌 Почему это полезно?
🔴Полная иммутабельность: поля конфигов больше нельзя случайно перезаписать.
🔴Минимум «шаблонного» кода: не нужны геттеры, сеттеры, toString()
, equals()
и т.д.
🔴Чёткая связь с Java 17+ и актуальными best practices.
💡 Как сделать:
1. Подключаем зависимость:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
<optional>true</optional>
</dependency>
record
с аннотацией:
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.context.properties.ConstructorBinding;
@ConstructorBinding
@ConfigurationProperties(prefix = "app.mail")
public record MailProperties(
String host,
int port,
String username,
String password
) {}
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Configuration;
@Configuration
@EnableConfigurationProperties(MailProperties.class)
public class AppConfig { }
application.yml
(или .properties
):
app:
mail:
host: smtp.example.com
port: 587
username: user@example.com
password: secret123
@ConstructorBinding
Spring не сможет смотать значения в record
’ы.@Validated
и JSR-303 аннотации (@NotNull
, @Min
и т.д.).record
заменил класс с 4 полями, геттерами и конструктором.record’а с разными префиксами или используйте @Profile
. В Spring Boot 3 этот подход «из коробки» работает наилучшим образом.В асинхронных или веб-сервисах на реактиве нужно быть осторожным: SecurityContext
не “переходит” автоматически в новые потоки. Для этого используют SecurityContextRepository
и специальные методы в WebFlux.
⚠️ Важно: не храните SecurityContext
в сессии, если у вас stateless-приложение (REST API). Вместо сессии используйте JWT или OAuth 2.0.
🧠 5. Авторизация: FilterSecurityInterceptor & AccessDecisionManagerFilterSecurityInterceptor
запускается в конце цепочки фильтров и проверяет доступ к URL. Он запрашивает у SecurityMetadataSource
список необходимых ролей для данного эндпоинта (Spring на основании @PreAuthorize
, HttpSecurity
конфигурации или XML). Затем передаёт дело в AccessDecisionManager
(по умолчанию AffirmativeBased), который опрашивает список AccessDecisionVoter
(например, RoleVoter
для проверок ролей, WebExpressionVoter
для SpEL).
FilterSecurityInterceptor
└─> SecurityMetadataSource (что нужно: ROLE_ADMIN)
└─> AccessDecisionManager.vote()
├─ RoleVoter.vote() → совпадает?
└─ WebExpressionVoter.vote() → SpEL-выражения?
AffirmativeBased
отпускает запрос (по умолчанию). Можно менять стратегию на Consensus
или Unanimous
.@WithMockUser(roles = "ADMIN")
и проверять, что нужный эндпоинт доступен.
@EnableMethodSecurity // (Spring Boot 3+) вместо @EnableGlobalMethodSecurity
public class SecurityConfig { ... }
// Где-то в сервисе:
@PreAuthorize("hasRole('ADMIN') and #id == principal.id")
public void deleteUser(Long id) { ... }
@PreAuthorize
/ @PostAuthorize
/ @Secured
/ @RolesAllowed
— все используют тот же механизм Voter’ов, но проверяют уже на методах сервиса.PasswordEncoder
с алгоритмами Argon2 или BCrypt:
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder(12);
}
SecurityContextPersistenceFilter
хранит контекст в сессии. Удобно для монолитов с классическим web-приложением.SessionCreationPolicy.STATELESS
, используем BearerTokenAuthenticationFilter
, аутентификация и авторизация проверяются по JWT в каждом запросе.SecurityFilterChain
с oauth2ResourceServer().jwt()
.spring-boot-starter-oauth2-resource-server
.issuer-uri
или jwk-set-uri
в application.yml
.
spring:
security:
oauth2:
resourceserver:
jwt:
issuer-uri: https://keycloak.example.com/realms/myrealm
POST
, PUT
, DELETE
). Для stateless-API его обычно отключают:
http.csrf().disable();
<input type="hidden" name="${_csrf.parameterName}" value="${_csrf.token}"/>
logging.level.org.springframework.security=DEBUG
«Я слышу свой код»: как работает Java-программист, потерявший зрение
Константин Евтеев собирает Java-код с помощью диктора NVDA, редактирует его в Блокноте и передает на Linux по SSH через самописные bash-скрипты. После потери зрения он не потерял интереса к жизни и желания быть полезным и выстроил собственную инженерную экосистему: оглавления по строкам .txt-файлами, навигация по main и маленьким методам, отладка на слух.
https://habr.com/ru/companies/axiomjdk/articles/913748/
👉@BookJava
🚀 Подборка Telegram каналов для программистов
Системное администрирование, DevOps 📌
/channel/bash_srv Bash Советы
/channel/win_sysadmin Системный Администратор Windows
/channel/sysadmin_girl Девочка Сисадмин
/channel/srv_admin_linux Админские угодья
/channel/linux_srv Типичный Сисадмин
/channel/devopslib Библиотека девопса | DevOps, SRE, Sysadmin
/channel/linux_odmin Linux: Системный администратор
/channel/devops_star DevOps Star (Звезда Девопса)
/channel/i_linux Системный администратор
/channel/linuxchmod Linux
/channel/sys_adminos Системный Администратор
/channel/tipsysdmin Типичный Сисадмин (фото железа, было/стало)
/channel/sysadminof Книги для админов, полезные материалы
/channel/i_odmin Все для системного администратора
/channel/i_odmin_book Библиотека Системного Администратора
/channel/i_odmin_chat Чат системных администраторов
/channel/i_DevOps DevOps: Пишем о Docker, Kubernetes и др.
/channel/sysadminoff Новости Линукс Linux
1C разработка 📌
/channel/odin1C_rus Cтатьи, курсы, советы, шаблоны кода 1С
/channel/DevLab1C 1С:Предприятие 8
/channel/razrab_1C 1C Разработчик
/channel/buh1C_prog 1C Программист | Бухгалтерия и Учёт
/channel/rabota1C_rus Вакансии для программистов 1С
Программирование C++📌
/channel/cpp_lib Библиотека C/C++ разработчика
/channel/cpp_knigi Книги для программистов C/C++
/channel/cpp_geek Учим C/C++ на примерах
Программирование Python 📌
/channel/pythonofff Python академия.
/channel/BookPython Библиотека Python разработчика
/channel/python_real Python подборки на русском и английском
/channel/python_360 Книги по Python
Java разработка 📌
/channel/BookJava Библиотека Java разработчика
/channel/java_360 Книги по Java Rus
/channel/java_geek Учим Java на примерах
GitHub Сообщество 📌
/channel/Githublib Интересное из GitHub
Базы данных (Data Base) 📌
/channel/database_info Все про базы данных
Мобильная разработка: iOS, Android 📌
/channel/developer_mobila Мобильная разработка
/channel/kotlin_lib Подборки полезного материала по Kotlin
Фронтенд разработка 📌
/channel/frontend_1 Подборки для frontend разработчиков
/channel/frontend_sovet Frontend советы, примеры и практика!
/channel/React_lib Подборки по React js и все что с ним связано
Разработка игр 📌
/channel/game_devv Все о разработке игр
Библиотеки 📌
/channel/book_for_dev Книги для программистов Rus
/channel/programmist_of Книги по программированию
/channel/proglb Библиотека программиста
/channel/bfbook Книги для программистов
БигДата, машинное обучение 📌
/channel/bigdata_1 Big Data, Machine Learning
Программирование 📌
/channel/bookflow Лекции, видеоуроки, доклады с IT конференций
/channel/rust_lib Полезный контент по программированию на Rust
/channel/golang_lib Библиотека Go (Golang) разработчика
/channel/itmozg Программисты, дизайнеры, новости из мира IT
/channel/php_lib Библиотека PHP программиста 👨🏼💻👩💻
/channel/nodejs_lib Подборки по Node js и все что с ним связано
/channel/ruby_lib Библиотека Ruby программиста
/channel/lifeproger Жизнь программиста. Авторский канал.
QA, тестирование 📌
/channel/testlab_qa Библиотека тестировщика
Шутки программистов 📌
/channel/itumor Шутки программистов
Защита, взлом, безопасность 📌
/channel/thehaking Канал о кибербезопасности
/channel/xakep_2 Хакер Free
Книги, статьи для дизайнеров 📌
/channel/ux_web Статьи, книги для дизайнеров
Математика 📌
/channel/Pomatematike Канал по математике
/channel/phis_mat Обучающие видео, книги по Физике и Математике
/channel/matgeoru Математика | Геометрия | Логика
Excel лайфхак📌
/channel/Excel_lifehack
/channel/mir_teh Мир технологий (Technology World)
Вакансии 📌
/channel/sysadmin_rabota Системный Администратор
/channel/progjob Вакансии в IT
🗑 Понимание различных сборщиков мусора в Java
🔵 Serial Garbage Collector: Лучший вариант для однопоточных приложений с небольшими кучами. Он использует один поток для выполнения как малых, так и больших сборок мусора, что приводит к значительным паузам, но минимальной нагрузке на систему.
🔵 Parallel Garbage Collector: Подходит для приложений с высокими требованиями к пропускной способности. Использует несколько потоков для выполнения как малых, так и больших сборок мусора, уменьшая время пауз, но при этом увеличивая использование CPU.
🔵 Concurrent Mark-Sweep (CMS) Garbage Collector: Разработан для минимизации пауз за счёт выполнения основной части работы по сборке мусора параллельно с выполнением приложений. Подходит для приложений, где критически важна низкая задержка.
🔵 G1 Garbage Collector: Сбалансированный сборщик мусора, который стремится обеспечить предсказуемое время пауз, разделяя кучу на регионы и выполняя сборку мусора поэтапно. Является хорошим выбором по умолчанию для большинства приложений.
🔵 Z Garbage Collector и Shenandoah: Сборщики мусора с ультранизкой задержкой, разработанные для работы с большими кучами. Основная часть работы по сборке мусора выполняется параллельно, что позволяет минимизировать время пауз даже при очень больших кучах.
👉@BookJava
VK Weekend Offer: отправьте заявку, пройдите интервью и получите офер!
28–29 июня VK проведёт Weekend Offer для бэкендеров с опытом от трёх лет. Участников со знанием Java, Go, Python или C++ ждут технические собеседования, знакомство с продуктами и, если всё сложится, офер уже в конце выходных.
Ребята много лет создают облачные решения, системы рекомендаций и поисковые движки — всё с миллионами пользователей в проде — и сейчас ищут новых коллег. Поэтому оставляйте заявку до 25 июня, чтобы попасть в команду за выходные!
Подробности — на сайте.
🧠 JPA Batch Insert: ускоряем и защищаем от OOM
📌 Настройка Hibernate
Добавьте в application.yml
или properties
:
spring:
jpa:
properties:
hibernate.jdbc.batch_size: 50 # размер пакета
hibernate.order_inserts: true # группировка INSERT’ов
hibernate.order_updates: true # группировка UPDATE’ов
@Service
@RequiredArgsConstructor
public class OrderService {
private final EntityManager em;
private static final int BATCH_SIZE = 50;
@Transactional
public void saveAll(List<Order> orders) {
for (int i = 0; i < orders.size(); i++) {
em.persist(orders.get(i));
if (i > 0 && i % BATCH_SIZE == 0) {
em.flush();
em.clear(); // освобождаем persistence-context
}
}
em.flush();
em.clear();
}
}
flush()
выталкивает пакеты в БД,clear()
освобождает ОЗУ от управляемых сущностей.@SequenceGenerator
с allocationSize
.-Dorg.hibernate.SQL=DEBUG
и -Dhibernate.format_sql=true
— вы увидите групповые INSERT
вместо множества одиночных.📌 CRaC (Coordinated Restore at Checkpoint) — горячая JVM-фишка для сверхбыстрого cold-start: сохраняем состояние приложения после инициализации и моментально «восстанавливаем» при рестарте.
🧠 Как это работает
1. JVM создаёт снимок (checkpoint) всего heap- и native-состояния сразу после bootstrap и bean-инициализации.
2. При рестарте JVM грузит этот снимок вместо полной загрузки классов и прогрева JIT.
💡 Подключение в Java 21+
1. Включите экспериментальный модуль:
--add-modules jdk.crac
--enable-preview
CheckpointListener
для чистки и восстановления ресурсов:
import jdk.crac.Core;
import jdk.crac.Control;
import jdk.crac.CheckpointListener;
import jdk.crac.Context;
import org.springframework.stereotype.Component;
@Component
public class CracHandler implements CheckpointListener {
@Override
public void beforeCheckpoint(Context<?> ctx) {
// 📌 Закрываем пулы, Flush в БД, отписываемся от очередей
}
@Override
public void afterRestore(Context<?> ctx) {
// 💡 Реинициализируем пулы, повторная регистрация listeners
}
}
// Регистрация слушателя
Core.getGlobalContext().register(new CracHandler());
# Сохраняем checkpoint
java \
--add-modules jdk.crac \
--enable-preview \
-XX:CRaCCheckpointToDir=crac-checkpoint \
-jar app.jar
# Восстанавливаем из него
java \
--add-modules jdk.crac \
--enable-preview \
-XX:CRaCRestoreFrom=crac-checkpoint \
-jar app.jar
📌 Stream.toList() vs Collectors.toList() — безопасная замена?
🧠 В Java 16+ появился метод Stream.toList()
, который собирает элементы потока в список. Раньше мы писали:
List<String> list = stream.collect(Collectors.toList());
List<String> list = stream.toList();
toList()
возвращает unmodifiable List
— любые add()/remove()
вылетят UnsupportedOperationException
.collect(Collectors.toList())
или
stream.collect(Collectors.toCollection(ArrayList::new));
toList()
не допускает null
и бросит NPE при встрече null
в потоке. Collectors.toList()
сохранит null
без ошибок.Stream.toList()
гарантированно создаёт новый список с точным размером, а Collectors.toList()
лишь «может» вернуть любой List
(часто ArrayList
, но без чётких гарантий).toList()
.null
— смело переходите на toList()
для более лаконичного и потенциально более эффективного кода.toList()
везде, где нужен только чтение — получилось короче и понятнее.Test Driven Development (TDD) in Java
Creating a Queue Abstract Data Type class
Introduction to Test Doubles Dummies and Stubs
Introduction to Test Doubles Spies
Introduction to Test Doubles Mocks
Introduction to Test Doubles - Fakes
источник
👉@BookJava
📚 Эффективное сжатие текста: код Хаффмана в действии
Приглашаем на открытый урок.
🗓 11 июня в 20:00 МСК
🆓 Бесплатно. Урок в рамках старта курса «Алгоритмы и структуры данных».
На этом вебинаре мы продолжим разработку архиватора, реализовав код Хаффмана.
✔️ Рассмотрим, как построить дерево кодов, где частота появления символов определяет их битовое представление.
✔️ Интегрируем алгоритм в наш архиватор и проведем сравнительное тестирование с RLE.
✔️ Увидим, как эффективно работает код Хаффмана на текстовых файлах и других типах данных.
Отличная возможность изучить продвинутые древовидные структуры данных на практическом примере.
Развивайте алгоритмическое мышление, увеличивайте производительность программ.
🎁 Всем участникам вебинара дарим промокод, который дает скидку на обучение - Algo5
👉 Регистрация на вебинар: https://vk.cc/cMzyUv
Реклама. ООО «Отус онлайн-образование», ОГРН 1177746618576
Структурированное логирование в Spring Boot 3.5.5
Spring Boot 3.5.5 приносит улучшенное структурированное логирование.
Чтобы его включить, добавьте следующее в ваш application.yml
:
Это обеспечивает более чистые, структурированные логи, что делает их проще для разбора инструментами вроде ELK, Grafana или Datadog.
👉@BookJava
Bulkhead — это паттерн из мира устойчивых систем, цель которого — изолировать сбои в одном компоненте, чтобы они не “затопили” всю систему. Сейчас я покажу вам несколько способов его реализации и подсвечу неочевидный момент при работе с любыми паттернами.
🧠 Концепция: представьте корабль с отсековыми переборками (bulkheads). Если вода просачивается в один отсек, остальные остаются сухими, и судно всё ещё может плыть. В мире Java/Spring это означает: ограничивать ресурсы (пулы потоков, соединения, очереди) для каждого сервиса/метода, чтобы при пике нагрузки или ошибках нагрузка не разошлась по всей системе.
📌 Способ 1: отдельные пул-экзекьюторы
@Configuration
public class BulkheadConfig {
@Bean("serviceAExecutor")
public ThreadPoolTaskExecutor serviceAExecutor() {
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
executor.setCorePoolSize(10);
executor.setMaxPoolSize(20);
executor.setQueueCapacity(50);
executor.setThreadNamePrefix("svcA-");
executor.initialize();
return executor;
}
}
@Service
public class ServiceA {
@Autowired @Qualifier("serviceAExecutor")
private Executor executor;
public CompletableFuture<String> callExternal() {
return CompletableFuture.supplyAsync(() -> {
// долгий/ненадежный вызов
return externalClient.fetchData();
}, executor);
}
}
RejectedExecutionHandler
по необходимости.
resilience4j.bulkhead.instances:
myServiceBulkhead:
maxConcurrentCalls: 5
maxWaitDuration: 100ms
@Service
public class MyService {
private final Bulkhead bulkhead;
public MyService(BulkheadRegistry registry) {
this.bulkhead = registry.bulkhead("myServiceBulkhead");
}
public String process() {
return Bulkhead.decorateSupplier(bulkhead, () -> {
// защищенный вызов
return externalClient.process();
}).get();
}
}
maxConcurrentCalls
слишком маленьким, часть запросов будет сразу отвергаться с BulkheadFullException
. Неочевидный момент: нужно мониторить реальную нагрузку и подбирать значения, а не копировать из гугла.
@Bulkhead(name = "myServiceBulkhead", type = Bulkhead.Type.SEMAPHORE)
public String annotatedProcess() { … }
resilience4j.bulkhead.instances:
myThreadPoolBulkhead:
maxThreadPoolSize: 10
queueCapacity: 20
@Bulkhead(name = "myThreadPoolBulkhead", type = Bulkhead.Type.THREADPOOL)
public CompletionStage<String> asyncProcess() { … }
30 лет Java: от провалившегося гаджета до фундамента разработки ПО
Некоторые языки программирования, например Rust, Go и TypeScript, считаются крутыми. Другие, в том числе Cobol и Java, «скучны». Однако пусть Java, которому 23 мая этого года исполнилось тридцать лет, может, и не самый захватывающий язык, он остаётся одним из самых важных.
Путь Java начался 23 мая 1995 года, когда его выпустила компания Sun Microsystems. За прошедшее время благодаря удачному видению разработчиков и адаптивности он превратился из нишевого проекта для потребительской электроники в мощный фундамент энтерпрайз-, облачной и веб-разработки.
Хоть Java исполнилось тридцать, его история гораздо дольше. Корнями этот язык уходит в 1991 год, когда инженеры Sun Джеймс Гослинг, Майк Шеридан и Патрик Ноутон приступили к созданию языка для интерактивного телевидения и встроенных устройств. Этот проект назвали Green Project. Его цель заключалась не столько в написании нового языка, сколько в создании того, что бы мы сегодня назвали контроллером Интернета вещей. Ещё один разработчик Java Тим Линдхольм, описал его как «своего рода гибрид между КПК и универсальным пультом дистанционного управления».
https://habr.com/ru/articles/914970/
original https://www.zdnet.com/article/java-at-30-how-a-language-designed-for-a-failed-gadget-became-a-global-powerhouse/
👉@BookJava
🔍Тестовое собеседование с Java-разработчиком из Т1 Иннотех уже завтра
4 июня(уже завтра!) в 19:00 по мск приходи онлайн на открытое собеседование, чтобы посмотреть на настоящее интервью на Middle Java-разработчика.
Как это будет:
📂 Илья Аров, старший разработчик в Т1, будет задавать реальные вопросы и задачи разработчику-добровольцу
📂 Илья будет комментировать каждый ответ респондента, чтобы дать понять чего от вас ожидает собеседующий на интервью
📂 В конце можно будет задать любой вопрос Илье
Это бесплатно. Эфир проходит в рамках менторской программы от ШОРТКАТ для Java-разработчиков, которые хотят повысить свой грейд, ЗП и прокачать скиллы.
Переходи в нашего бота, чтобы получить ссылку на эфир → @shortcut_sh_bot
Реклама. ООО "ШОРТКАТ", ИНН: 9731139396, erid: 2VtzqvL6bDw
Релиз через два дня. Код готов. Почти...
Остались тесты. Ну, точнее — покрытие. Потому что QA уже дышит в затылок, а ты сидишь и выбираешь: спать или корпеть до утра.
Explyt Test умеет создавать тесты под твой код — сам. Быстро. В IDE. Без плясок.
Хочешь, чтобы релиз прошёл, а не пролетел? Попробуй бесплатно! 👉 explyt.ai
📌 Spring Security: основная архитектура
🧠 1. SecurityFilterChain & FilterChainProxy
Spring Security строит всё вокруг цепочки фильтров (FilterChainProxy). При запросе к приложению запрос проходит через набор фильтров, каждый из которых отвечает за свой кусок логики:
* ⚙️ ChannelProcessingFilter – перенаправление на HTTPS, если нужно.
* ⚙️ SecurityContextPersistenceFilter – загружает/сохраняет SecurityContext
(где хранится Authentication
).
* ⚙️ UsernamePasswordAuthenticationFilter – обрабатывает форму логина (если вы используете formLogin).
* ⚙️ BasicAuthenticationFilter – поддерживает HTTP Basic (для REST).
* ⚙️ BearerTokenAuthenticationFilter (Spring Boot 3+) – для JWT/OAuth2 Bearer-токенов.
* ⚙️ ExceptionTranslationFilter – перехватывает AccessDeniedException и AuthenticationException, перенаправляет на страницу логина или возвращает 401.
* ⚙️ FilterSecurityInterceptor – проверяет, есть ли у аутентифицированного пользователя разрешение (ROLE_*) для доступа к ресурсу.
Каждый фильтр решает конкретную задачу, и порядок важен: если, например, фильтр авторизации (FilterSecurityInterceptor) стоит раньше, чем фильтр аутентификации, вы получите неожиданный отказ.
💡 Современный подход (Spring Boot 3+):
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
http
.csrf().disable()
.authorizeHttpRequests(auth -> auth
.requestMatchers("/public/**").permitAll()
.anyRequest().authenticated()
)
.sessionManagement(sess -> sess.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
.oauth2ResourceServer(oauth2 -> oauth2.jwt()); // JWT из OIDC/JWK
return http.build();
}
UsernamePasswordAuthenticationFilter
(или другой аутентификатор) получает учётные данные, он создает UsernamePasswordAuthenticationToken
с неверифицированными (unauthenticated) флагом. Затем передаёт этот токен в AuthenticationManager
:
UsernamePasswordAuthenticationFilter → AuthenticationManager.authenticate()
AuthenticationManager
по умолчанию — это ProviderManager
, который хранит список AuthenticationProvider
(например, DaoAuthenticationProvider
для UserDetailsService или JwtAuthenticationProvider
для токенов). Каждый Provider
пытается аутентифицировать токен, и если успешно, возвращает уже аутентифицированный Authentication
с authorities.AuthenticationProvider
и зарегистрируйте его перед DaoAuthenticationProvider
.DaoAuthenticationProvider
опирается на UserDetailsService
(или ReactiveUserDetailsService
в WebFlux), чтобы получить UserDetails
(имя, пароль, роли, статус аккаунта). В Java 17+ можно пользоваться Map.of(...)
или List.of(...)
, но в реальных проектах лучше хранить в БД через JPA/Hibernate.
@Service
public class CustomUserDetailsService implements UserDetailsService {
private final UserRepository repo;
@Override
public UserDetails loadUserByUsername(String username) {
UserEntity user = repo.findByUsername(username)
.orElseThrow(() -> new UsernameNotFoundException("User not found"));
return User.withUsername(user.getUsername())
.password(user.getPassword())
.authorities(user.getRoles().toArray(new String[0]))
.accountLocked(!user.isAccountNonLocked())
.build();
}
}
SecurityContext
в SecurityContextHolder
. По умолчанию используется стратегия MODE_THREADLOCAL
, т.е. контекст привязан к текущему потоку.Читать полностью…
SecurityContextHolder.getContext().setAuthentication(authenticatedToken);
🧠 Чем Spring Native Image отличается от обычного Spring-приложения?
📌 Обычный Spring:
* Запускается на JVM, динамически загружает классы, использует рефлексию.
* Медленный старт (секунды), выше потребление памяти.
* Подходит для сложной логики с динамическим поведением (настройки, рефлексия, прокси).
📌 Spring Native Image (GraalVM):
* Компиляция в нативный бинарник.
* ⚡️ Мгновенный старт (миллисекунды), низкое потребление памяти.
* Отсутствие динамики: ограничения в рефлексии, прокси и динамической загрузке.
💡 Когда что использовать?
* Native Image — идеален для микросервисов и Serverless-приложений.
* Обычный JVM Spring — когда важна максимальная гибкость и динамика.
⚠️ Помни, что Native Image требует больше усилий по настройке и ограничений на библиотеки.
👉@BookJava
Как не завалить приложение из-за N+1 запросов в Hibernate 🧨
N+1 проблема — больной зуб почти любого Java-разработчика. Hibernate делает жизнь проще, пока не сталкиваешься с этим 👻
📌 Суть проблемы
Допустим, у тебя есть сущности User
и связанные с ними сущности Order
. При запросе пользователей:
List<User> users = userRepository.findAll(); // Один запрос
for(User user : users) {
List<Order> orders = user.getOrders(); // +1 запрос на каждого пользователя!
}
@Query("SELECT u FROM User u JOIN FETCH u.orders")
List<User> findAllWithOrders();
@EntityGraph(attributePaths = {"orders"})
List<User> findAll();
spring.jpa.properties.hibernate.default_batch_fetch_size=20
Серия статей: «Дюк, вынеси мусор!»
Все, что нужно знать о сборке мусора в Java
🔹 Если работа с JVM для тебя не просто слова, а параметры вроде Xmx
и Xms
ты прописываешь с закрытыми глазами, самое время разобраться, что же реально происходит с памятью в Java и как новые сборщики мусора могут повлиять на производительность твоего приложения.
🗂 В этой серии — подробный разбор всех современных GC, доступных в Java HotSpot VM: от базовых до самых продвинутых. Для каждого — объяснение принципов, сценарии применения, плюсы и минусы, а также практические советы по настройке.
Список статей:
1️⃣ Введение
2️⃣ Serial GC и Parallel GC
3️⃣ CMS и G1
4️⃣ ZGC
5️⃣ Epsilon GC
6️⃣ Shenandoah GC
👉@BookJava