Библиотека джависта | Java, Spring, Maven, Hibernate
22.8K subscribers
2.31K photos
51 videos
47 files
3.33K links
Все самое полезное для Java-разработчика в одном канале.

Список наших каналов: https://t.me/proglibrary/9197

Для обратной связи: @proglibrary_feeedback_bot

По рекламе: @proglib_adv

РКН: https://gosuslugi.ru/snet/67a5bbda1b17b35b6c1a55c4
Download Telegram
📌 6 принципов безопасности API

API — это не просто интерфейс, а ворота в вашу систему. Без должной защиты они становятся уязвимой точкой для атак.

🔐 Ключевые принципы безопасности API

— Использование HTTPS
— Аутентификация и авторизация
— Ограничение запросов
— Валидация входных данных
— Управление доступом на основе ролей (RBAC)
— Мониторинг и логирование

🔗 Подробнее в статье

══════ Навигация ══════
ВакансииЗадачиСобесы

🐸 Библиотека джависта

#CoreJava
Please open Telegram to view this post
VIEW IN TELEGRAM
👍9🔥1👏1
🖥 На GitHub есть отличный репозиторий

useful-java-links — там собраны лучшие материалы по Java. В репозитории найдёте библиотеки, фреймворки, инструменты и образовательные материалы по разным направлениям

🔹 Отдельный акцент сделан на решениях для работы с СУБД, поисковиками, технологиями обработки больших данных и ML. Список регулярно пополняется новыми ссылками и остаётся актуальным.

══════ Навигация ══════
ВакансииЗадачиСобесы

🐸 Библиотека джависта

#CoreJava
Please open Telegram to view this post
VIEW IN TELEGRAM
👍5🔥4👏1
🔥 Топ-10 интересных ошибок в реальных проектах

PVS-Studio собрали подборку самых интересных багов 2025 года из крупных Open Source проектов.

Тут есть всё: неочевидные языковые нюансы, которые ломают логику, ошибки из-за особенностей инициализации, проблемы с типами там, где их не ждёшь. Каждая ошибка разобрана с примерами кода и объяснением, почему она возникла.

Особенно понравится тем, кто любит копаться в деталях языка и понимать, как на самом деле работает JVM под капотом.

🔗 Читать на Habr

══════ Навигация ══════
ВакансииЗадачиСобесы

🐸 Библиотека джависта

#CoreJava
Please open Telegram to view this post
VIEW IN TELEGRAM
😁5👍2🔥1
🖥 От REST к gRPC и GraphQL

В статье рассмотрены 3 подхода к построению API. Также автор показывает, как объединить gRPC и GraphQL, чтобы фронт получал гибкие запросы, а бэк получал скорость и эффективность.

🔗 Подробнее в статье

══════ Навигация ══════
ВакансииЗадачиСобесы

🐸 Библиотека джависта

#CoreJava
Please open Telegram to view this post
VIEW IN TELEGRAM
👍10🔥21👏1
💭 Микросервисы: Spring Cloud

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

— Как сервису А узнать, где сейчас живёт сервис Б (тем более, если адреса постоянно меняются)?
— Как не утонуть в километрах кода для HTTP-запросов?
— Как фронтенду работать с этой кучей сервисов?

Для этого есть три главных инструмента.

1️⃣ Eureka: телефонная книга (Service Discovery)

В облаке сервисы постоянно перезапускаются, меняют IP-адреса и порты. Хардкодить http://localhost:8082 не вариант.

Eureka Server работает как справочная служба

🔹 При старте каждый сервис стучится в Eureka: «Я на связи, вот мой адрес».
🔹 Когда сервису А нужно достучаться до Б, он обращается к Eureka: «Подскажи, где сейчас сервис Б».

Нужно добавить аннотацию @EnableDiscoveryClient для работы Eureka. Сервисы будут находить друг друга по имени, а не по IP.

2️⃣ OpenFeign: инструмент для связи

Для отправки запросов между сервисами можно использовать RestTemplate. Но код получается довольно громоздким и не типизированным.

Вместо этого можно использовать Feign. Он позволяет вызывать удаленный REST-сервис так, будто это обычный метод интерфейса. А вся реализация генерируется под капотом.

@FeignClient(name = "account-service") // Имя сервиса в Eureka
public interface AccountClient {

@GetMapping("/account/{userId}")
List<Account> getAccount(@PathVariable Long userId);
}

// Использование в сервисе:
List<Account> accounts = accountClient.getAccount(13L);


3️⃣ API Gateway: единая точка входа

Теперь следующая проблема: сервисы между собой нормально общаются, а вот фронт всё ещё не знает кому точно отправлять запрос. Можно сложить на фронт адреса каждого сервиса, но это сложно и небезопасно.

Тут на помощь и приходит Spring Cloud Gateway. Он выступает как КПП. Фронт стучится только в него, а он уже сам разруливает запрос к нужным сервисам.

Например: запросы на /users/** идут в UserService.

🔹 Что он делает

Маршрутизация: смотрит на путь запроса и решает, в какой именно сервис его отправить.
— Безопасность: проверяет JWT токен один раз на входе.
Rate Limiting: защита от спама (например, максимум 10 запросов в секунду с одного IP).

📌 Как это работает всё вместе:

1. Сервисы стартуют и сообщают Eureka о себе.
2. Фронт отправляет запрос в Gateway.
3. Gateway узнаёт у Eureka, где живёт нужный сервис, и перенаправляет запрос.
4. Когда сервисам нужно поговорить друг с другом, в дело вступает Feign.

══════ Навигация ══════
ВакансииЗадачиСобесы

🐸 Библиотека джависта

#CoreJava
Please open Telegram to view this post
VIEW IN TELEGRAM
👍15🔥31👏1
⚙️ YAML vs Properties: что выбрать для Spring Boot конфигурации?

Недавно в очередной раз столкнулся с холиваром: "Зачем ты переписал properties в yaml?" Давайте разбираться.

🔹 Properties — старая гвардия


spring.datasource.url=jdbc:postgresql://localhost:5432/mydb
spring.datasource.username=admin
spring.datasource.password=secret
spring.jpa.hibernate.ddl-auto=validate
spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.PostgreSQLDialect


✔️ Читается IDE из коробки, нет проблем с отступами, работает везде и всегда.

Повторяющиеся префиксы превращают файл в лапшу, особенно когда конфига много.

🔹 YAML — современный подход

spring:
datasource:
url: jdbc:postgresql://localhost:5432/mydb
username: admin
password: secret
jpa:
hibernate:
ddl-auto: validate
properties:
hibernate:
dialect: org.hibernate.dialect.PostgreSQLDialect


✔️ Структурированность, читаемость, легко работать со списками и сложными объектами. Можно описать несколько профилей в одном файле через ---.

Чувствителен к отступам (один лишний пробел — и привет, ошибка на старте), нельзя использовать с @PropertySource.

🔹 Технические нюансы

— Парсинг и производительность: properties парсится через java.util.Properties (простой key-value), YAML требует SnakeYAML библиотеку. На старте приложения разница незаметна, но YAML чуть медленнее из-за построения объектной модели.

— Приоритет загрузки: Spring Boot загружает конфиги в порядке: application.properties → application.yml → application-{profile}.properties → application-{profile}.yml. Если есть оба формата, properties имеет приоритет над yaml для одинаковых ключей.

— Списки и массивы: в YAML естественная запись списков, в properties приходится использовать индексы.

— Placeholder resolution: оба формата поддерживают ${variable:default}, но в YAML нужно быть осторожным с экранированием двоеточий в URL.

— Невалидный синтаксис: properties молча проигнорирует строку без =, YAML упадёт с исключением при старте приложения — это и минус, и плюс одновременно.

@PropertySource ограничение: аннотация работает только с .properties, для YAML нужно писать кастомный PropertySourceFactory.

Что выбрать?

YAML для основной конфигурации приложения — структура и удобство работы со сложными объектами того стоят. Properties для специфичных кейсов: custom property sources с @PropertySource, legacy интеграции, простые CI/CD переменные, конфиги для библиотек без Spring контекста.

В production проектах часто вижу гибридный подход: application.yml для базовой структуры + application-{env}.properties для переопределения специфичных параметров окружения через environment variables.

💬 Какой формат и в каких кейсах предпочитаете?

══════ Навигация ══════
ВакансииЗадачиСобесы

🐸 Библиотека джависта

#CoreJava
Please open Telegram to view this post
VIEW IN TELEGRAM
👍144🔥1
📈 Продвинутая Map

Если ты хоть раз создавал Map<String, List<String>>, то пост для тебя.

Признай, каждый когда-нибудь писал:

Map<String, List<String>> userTags = new HashMap<>();
userTags.computeIfAbsent("user123", k -> new ArrayList<>()).add("premium");
userTags.computeIfAbsent("user123", k -> new ArrayList<>()).add("verified");


Или ещё хуже:

if (!userTags.containsKey("user123")) {
userTags.put("user123", new ArrayList<>());
}
userTags.get("user123").add("premium");


Apache Commons Collections давно придумал MultiValuedMap.

🔹 Что это такое

MultiValuedMap<K, V> — это структура данных, которая позволяет хранить несколько значений для одного ключа. По сути, это Map<K, Collection<V>>, но с нормальным API.

MultiValuedMap<String, String> userTags = new ArrayListValuedHashMap<>();

userTags.put("user123", "premium");
userTags.put("user123", "verified");
userTags.put("user123", "early-adopter");

// Получаем все теги разом
Collection<String> tags = userTags.get("user123");
// [premium, verified, early-adopter]


Никаких computeIfAbsent, никаких проверок на null. Просто работает.

🔹 Что умеет

— Добавление без боли:
multiMap.put("key", "value1");
multiMap.put("key", "value2"); // не перезатирает предыдущее значение


— Массовые операции:
multiMap.putAll("user456", Arrays.asList("admin", "moderator"));


— Проверка наличия:
multiMap.containsMapping("user123", "premium"); // true/false


— Удаление конкретного значения:
multiMap.removeMapping("user123", "premium");


— Получение всех значений:
Collection<String> allTags = multiMap.values(); 
// все значения из всех ключей


🔹 Реализации

ArrayListValuedHashMap<K, V> — значения хранятся в ArrayList, порядок сохраняется, дубликаты разрешены
HashSetValuedHashMap<K, V> — значения хранятся в HashSet, без дубликатов

══════ Навигация ══════
ВакансииЗадачиСобесы

🐸 Библиотека джависта

#CoreJava
Please open Telegram to view this post
VIEW IN TELEGRAM
🔥23👍11👏21
🚩 Pattern Matching в Switch

Раньше switch был ограничен примитивами и enum'ами. С Java 21 Pattern Matching стал стандартом, и это принципиально меняет подход к обработке полиморфных данных.

Что раньше:

Object obj = getObject();
String result;
if (obj instanceof String s) {
result = "String: " + s;
} else if (obj instanceof Integer i) {
result = "Int: " + i;
} else {
result = "Unknown";
}


✔️ Что теперь:

String result = switch (obj) {
case String s -> "String: " + s;
case Integer i -> "Int: " + i;
case null -> "Null!";
default -> "Unknown";
};


Но суть не в синтаксисе. Суть в гарантиях компилятора.

🔹 Охранные выражения (guarded patterns)

String classify(Object obj) {
return switch (obj) {
case String s when s.length() > 10 -> "Long string";
case String s -> "Short string";
case Integer i when i > 0 -> "Positive";
case Integer i -> "Non-positive";
case null -> "Null";
default -> "Other";
};
}


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

🔹 Record patterns — деструктуризация на месте

record Point(int x, int y) {}

String describe(Object obj) {
return switch (obj) {
case Point(int x, int y) when x == y ->
"Diagonal point";
case Point(int x, int y) ->
"Point at (%d, %d)".formatted(x, y);
default -> "Not a point";
};
}


Распаковали record прямо в case. Никаких геттеров, никаких промежуточных переменных.

🔹 Sealed классы + pattern matching = полнота проверок

sealed interface Result permits Success, Failure {}
record Success(String data) implements Result {}
record Failure(String error) implements Result {}

String handle(Result result) {
return switch (result) {
case Success(String data) -> "Got: " + data;
case Failure(String error) -> "Error: " + error;
// default не нужен - компилятор знает все варианты
};
}


Компилятор гарантирует, что вы обработали все случаи. Добавите новый класс в sealed иерархию — код не скомпилится, пока не обработаете его.

🔼 Производительность

JIT оптимизирует pattern matching свитчи агрессивно. В бенчмарках разница с if-else цепочками от 2x до 10x в пользу switch в зависимости от количества веток.

══════ Навигация ══════
ВакансииЗадачиСобесы

🐸 Библиотека джависта

#CoreJava
Please open Telegram to view this post
VIEW IN TELEGRAM
🔥14👍711👏1
🌐 Что происходит, когда вы вводите адрес сайта

Каждый раз, когда вы набираете https://example.com в браузере, за кулисами запускается целая цепочка событий.

1️⃣ Разбор адреса

Браузер делит URL на части:

▪️ https — протокол
▪️ example.com — домен
▪️ /page — путь к ресурсу

2️⃣ Поиск IP

Если IP не сохранён в кеше, браузер спрашивает DNS-сервер: «Где живёт example.com

3️⃣ Установление соединения

Создаётся TCP-соединение с сервером по IP и порту (80 для HTTP, 443 для HTTPS).

4️⃣ Запрос ресурса

Браузер отправляет HTTP-запрос: GET /page HTTP/1.1

5️⃣ Ответ сервера

Сервер возвращает HTML, CSS, JS и статус-код (например, 200 OK или 404 Not Found).

6️⃣ Рендеринг страницы

Браузер обрабатывает HTML, применяет стили и выполняет JavaScript.

7️⃣ Шифрование

Если сайт работает по HTTPS, соединение шифруется через SSL/TLS.

8️⃣ Кеширование

Браузер сохраняет ресурсы, чтобы при следующем визите всё грузилось быстрее.

══════ Навигация ══════
ВакансииЗадачиСобесы

🐸 Библиотека джависта

#CoreJava
Please open Telegram to view this post
VIEW IN TELEGRAM
👍122🔥1👏1
⚙️ RestTemplate vs WebClient

🔹 RestTemplate

javaRestTemplate restTemplate = new RestTemplate();
ResponseEntity<User> response = restTemplate.getForEntity(
"https://api.example.com/users/{id}",
User.class,
userId
);
User user = response.getBody();


✔️ Простота, привычность, работает из коробки. Идеален для простых CRUD операций.

Блокирующий I/O — поток висит и ждет ответа. При высоких нагрузках нужен большой thread pool. С 5.0 официально в maintenance mode.

🔹 WebClient

javaWebClient webClient = WebClient.create("https://api.example.com");
Mono<User> userMono = webClient.get()
.uri("/users/{id}", userId)
.retrieve()
.bodyToMono(User.class);


✔️ Неблокирующий I/O — поток освобождается и обрабатывает другие запросы. Отлично работает с реактивными стримами. Fluent API, удобная настройка retry, timeout, circuit breaker.

Кривая обучения — нужно понимать реактивные концепции (Mono, Flux). Сложнее дебажить, требует Project Reactor в зависимостях.

🔹 Технические нюансы

— Thread model: RestTemplate использует по потоку на запрос (blocking I/O). WebClient работает на event loop с малым числом потоков (по умолчанию CPU cores * 2).

— Performance: в синхронных сценариях разница минимальна. При параллельных запросах WebClient показывает x2-x5 прирост throughput за счёт эффективного использования потоков.

— Memory footprint: 1000 параллельных REST вызовов через RestTemplate = ~1000 МБ стека потоков. WebClient с той же нагрузкой — десятки МБ.

— Timeout configuration: RestTemplate требует настройки через ClientHttpRequestFactory. WebClient имеет встроенный .timeout(Duration) в цепочке вызовов.

— Error handling: RestTemplate выбрасывает исключения синхронно. WebClient возвращает Mono с ошибкой.

— Testing: RestTemplate легко мокается через MockRestServiceServer. WebClient требует понимания StepVerifier из reactor-test.

— Compatibility: RestTemplate работает везде. WebClient требует Spring WebFlux в classpath, даже если используешь его в обычном Spring MVC приложении.

Что выбрать?

WebClient если стартуешь новый проект, делаешь микросервисы с высокой нагрузкой, уже используешь reactive stack или планируешь масштабироваться. RestTemplate если поддерживаешь legacy код без реактивности, команда не знакома с Project Reactor, делаешь простой CRUD сервис с малой нагрузкой.

Главное помни: преждевременная оптимизация — корень зла. Если RestTemplate закрывает задачу, не усложняй. Но если упираешься в потоки на проде, время учить реактивщину.

══════ Навигация ══════
ВакансииЗадачиСобесы

🐸 Библиотека джависта

#CoreJava
Please open Telegram to view this post
VIEW IN TELEGRAM
👍6🔥32👏1
This media is not supported in your browser
VIEW IN TELEGRAM
🔼 Прокачай DevOps-скиллы на практике

Платформа с реальными DevOps-задачами. Кейсы по Linux, Docker и Kubernetes — всё как в бою, но с читами:

✔️ Автопроверка решений
✔️ Подсказки
✔️ Готовые решения

🔗 Попробовать

══════ Навигация ══════
ВакансииЗадачиСобесы

🐸 Библиотека джависта

#CoreJava
Please open Telegram to view this post
VIEW IN TELEGRAM
👍5🔥4👏1
🔬 Правильный вопрос про AI

Все исследования AI-ассистентов меряют одно: насколько быстрее ты закрываешь задачу. Метрика удобная, но она игнорирует 70–80% реальных затрат — поддержку, рефакторинг, устранение дефектов.

Исследование «Echoes of AI» (arXiv, 2025) спросило другое:

«Что будет, когда другой разработчик возьмёт AI-код и попытается его развивать?»

151 участник, 95% практикующие разработчики (не студенты). Java/Spring Boot проект, две фазы, настоящее РКИ. Одна группа пишет с AI, другая без. Потом третья группа без AI поддерживает и тот, и другой код.

Результат оказался неожиданный.

👉 Читать

══════ Навигация ══════
ВакансииЗадачиСобесы

🐸 Библиотека джависта

#CoreJava
Please open Telegram to view this post
VIEW IN TELEGRAM
👍61🔥1
🧩 Просто о сложном: Saga Pattern

Когда пользователь оформляет заказ, нужно списать деньги, зарезервировать товар и уведомить склад. Всё в разных сервисах. Что делать, если один из них упал?

Обычная транзакция тут не поможет. Добро пожаловать в Saga.

Идея проста: разбиваем большую распределённую транзакцию на цепочку маленьких локальных. Каждый шаг — это своя транзакция в своём сервисе. Если что-то пошло не так, то запускаем компенсирующие транзакции в обратном порядке.

🔹 Два подхода

1. Choreography (хореография)

Сервисы общаются через события. Каждый сам знает, что делать дальше.
OrderService → [OrderCreated] → PaymentService
PaymentService → [PaymentDone] → InventoryService
InventoryService → [Reserved] → NotificationService


✔️ Просто, нет единой точки отказа
Сложно отследить весь флоу, спагетти из событий

2. Orchestration (оркестрация)


Есть дирижёр — Saga Orchestrator. Он знает весь сценарий и командует сервисами.
public class OrderSagaOrchestrator {

public void execute(Order order) {
try {
paymentService.charge(order);
inventoryService.reserve(order);
notificationService.notify(order);
} catch (PaymentException e) {
// компенсация не нужна — деньги не списаны
} catch (InventoryException e) {
paymentService.refund(order); // компенсируем
}
}
}


✔️ Флоу виден в одном месте, легко дебажить
Оркестратор может стать узким местом

⚠️ Важно понимать: компенсирующая транзакция ≠ откат БД. Это бизнес-операция. Если деньги уже списаны, то мы не откатываем строку в БД, мы делаем возврат.

В Java-экосистеме Saga используют вместе с:

→ Apache Kafka / RabbitMQ — для событий между сервисами
→ Axon Framework — встроенная поддержка Saga из коробки
→ Spring State Machine — для управления состоянием оркестратора
→ Temporal / Conductor — оркестрация workflow на уровне инфраструктуры

🔹 Когда использовать

✔️ Микросервисная архитектура
✔️ Несколько БД, нет возможности использовать 2PC
✔️ Долгоживущие бизнес-транзакции
Монолит с одной БД — просто используй @Transactional

Saga — это не серебряная пуля, это компромисс. Ты жертвуешь изоляцией ради масштабируемости. Данные могут быть временно не консистентны и это нормально, если бизнес с этим согласен.

══════ Навигация ══════
ВакансииЗадачиСобесы

🐸 Библиотека джависта

#CoreJava
Please open Telegram to view this post
VIEW IN TELEGRAM
👍122🔥2👏1👾1
Сохраняйте шпаргалку по join'ам

Наглядная шпаргалка с примерами и визуализацией SQL JOIN.

══════ Навигация ══════
ВакансииЗадачиСобесы

🐸 Библиотека джависта

#CoreJava
Please open Telegram to view this post
VIEW IN TELEGRAM
👍1462🔥2
💥 Разрушим частый миф

Мне НЕ нужно разбираться с управлением памятью в Java, так как за меня всё делает GC


Garbage Collector (GC) действительно очищает неиспользуемые объекты, которые больше не имеют активных ссылок. Благодаря этому в Java не нужно вручную освобождать память, как в C++.

Несколько предпосылок к исходному тезису:

🔹 В других языках, где ручное управление памятью (C, C++), утечки очевидны — если забыл free(), память навсегда потеряна.
🔹 В Java GC работает автоматически, поэтому кажется, что он решает все проблемы сам.
🔹 "Ну раз GC есть, значит, про память можно не думать!" — типичная ошибка.

GC удаляет только те объекты, которые больше не имеют активных ссылок. Если же объект остаётся доступным, но фактически не используется, он будет занимать память до завершения работы приложения.

🔽 Несколько случаев утечек памяти

1️⃣ Статические коллекции (заполняем, но не чистим)

Если создать static List и постоянно добавлять в него объекты, они никогда не будут удалены GC, потому что статические поля живут весь срок жизни приложения.

public class MemoryLeak {
private static final List<byte[]> cache = new ArrayList<>();

public static void main(String[] args) {
while (true) {
cache.add(new byte[10 * 1024 * 1024]);
System.out.println("Добавили 10MB в кеш. Используемая память: " +
(Runtime.getRuntime().totalMemory() - Runtime.getRuntime().freeMemory()) / (1024 * 1024) + "MB");
}
}
}


Через пару минут — OutOfMemoryError

2️⃣ Потоковые переменные (ThreadLocal)

Объекты, хранящиеся в ThreadLocal, привязываются к потоку, а в пуле потоков они могут жить дольше, чем нужно.

public class ThreadLocalLeak {
private static final ThreadLocal<byte[]> threadLocalData = new ThreadLocal<>();

public static void main(String[] args) {
ExecutorService executor = Executors.newFixedThreadPool(5);

for (int i = 0; i < 10; i++) {
executor.execute(() -> {
threadLocalData.set(new byte[10 * 1024 * 1024]); // 10MB на поток
System.out.println("Память занята потоком!");
});
}

executor.shutdown();
}
}


Поток завершится, а память останется занята, потому что ThreadLocal не очищается автоматически.

3️⃣ Внутренние классы и "утекшие" ссылки

Если анонимный класс или лямбда-ссылка ссылается на внешний объект, она может мешать GC очистить его.

public class InnerClassLeak {
private String data = "Очень важные данные";

public void createAnonymousClass() {
Runnable task = new Runnable() {
@Override
public void run() {
System.out.println("Используем: " + data);
}
};
new Thread(task).start();
}
}


task ссылается на data, даже если InnerClassLeak больше не используется → GC не очистит объект.

👎 Миф разрушен. GC не всемогущий и даже с ним придётся изучать, как работать с памятью в Java.

══════ Навигация ══════
ВакансииЗадачиСобесы

🐸 Библиотека джависта

#CoreJava
Please open Telegram to view this post
VIEW IN TELEGRAM
👍175🔥3