🚀 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
Описываете внешний 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
🔥13👍6❤2
💡 Java I/O tip
Нужно быстро сделать копию файла?
В Java для этого есть
Что здесь важно:
*
*
* удобно для бэкапов, экспорта и временных копий перед обработкой данных
Пример:
Одна строка вместо ручного чтения и записи байтов.
#Java #Files
Нужно быстро сделать копию файла?
В Java для этого есть
Files.copy():
Path src = Path.of("data.csv");
Path backup = Path.of("data.csv.bak");
Files.copy(src, backup, StandardCopyOption.REPLACE_EXISTING);
Что здесь важно:
*
Path работает нормально на разных ОС*
REPLACE_EXISTING перезапишет файл, если backup уже есть* удобно для бэкапов, экспорта и временных копий перед обработкой данных
Пример:
data.csv → data.csv.bak
Одна строка вместо ручного чтения и записи байтов.
#Java #Files
👍8❤1
Для сериализаторов, DI-контейнеров и reflection-кода часто пишут так:
private static final Map<Class<?>, Metadata> CACHE =
new ConcurrentHashMap<>();
static Metadata metadata(Class<?> type) {
return CACHE.computeIfAbsent(type, Metadata::inspect);
}
Проблема: статическая
Map хранит сильные ссылки на Class. В приложениях с плагинами, hot reload или несколькими ClassLoader такой кэш может мешать выгрузке старых классов.Для данных, привязанных к конкретному классу, в Java есть
ClassValue:
private static final ClassValue<Metadata> METADATA =
new ClassValue<Metadata>() {
@Override
protected Metadata computeValue(Class<?> type) {
return Metadata.inspect(type);
}
};
static Metadata metadata(Class<?> type) {
return METADATA.get(type);
}
Что это даёт:
- ленивое вычисление значения;
- потокобезопасную установку результата;
- отсутствие глобальной карты с
Class в качестве сильных ключей;- возможность сбросить значение через
METADATA.remove(type).Важный нюанс:
computeValue() может выполниться несколько раз при гонке потоков, хотя сохранено будет одно значение. Поэтому внутри не должно быть необратимых побочных эффектов.ClassValue особенно полезен для кэширования reflection-метаданных, MethodHandle, сериализаторов и обработчиков аннотаций.#java #jvm #performance
Please open Telegram to view this post
VIEW IN TELEGRAM
👍7
Java-совет: сравнивайте объекты без риска получить `NullPointerException`
Обычный вызов
Безопаснее использовать
Метод корректно обрабатывает все случаи:
Это особенно полезно внутри
Правило простое: когда хотя бы одна сторона сравнения потенциально может быть
#Java #JavaTips #Programming
Обычный вызов
equals() опасен, если объект слева может быть null:
String a = null;
String b = "hi";
boolean result = a.equals(b); // NullPointerException
Безопаснее использовать
Objects.equals():
boolean result = Objects.equals(a, b); // false
Метод корректно обрабатывает все случаи:
Objects.equals(null, null); // true
Objects.equals("hi", "hi"); // true
Objects.equals("hi", null); // false
Это особенно полезно внутри
equals() у собственных классов:
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (!(o instanceof User user)) return false;
return Objects.equals(id, user.id)
&& Objects.equals(email, user.email);
}
Правило простое: когда хотя бы одна сторона сравнения потенциально может быть
null, используйте Objects.equals(a, b).#Java #JavaTips #Programming
🔥8❤4👍1🎉1
💡 Java-совет: сравнивайте `Instant` с нужной точностью
Так же можно сравнивать значения с точностью до часа или дня:
Важно:
#Java #JavaTime
Instant.equals() сравнивает время вплоть до наносекунд, поэтому даже небольшое различие даст false.
boolean sameMinute =
first.truncatedTo(ChronoUnit.MINUTES)
.equals(second.truncatedTo(ChronoUnit.MINUTES));
Так же можно сравнивать значения с точностью до часа или дня:
ChronoUnit.HOURS
ChronoUnit.DAYS
Важно:
Instant работает в UTC. Для проверки одного календарного дня в конкретном часовом поясе сначала преобразуйте значения в ZonedDateTime.#Java #JavaTime
👍8❤1🔥1
💡 Не используйте `Stream.peek()` для бизнес-логики
Для реальной обработки выбирайте:
*
*
*
Код со скрытыми побочными эффектами сложнее читать, тестировать и поддерживать.
#Java #Streams
peek() предназначен в первую очередь для отладки: он выполняет побочное действие внутри цепочки и зависит от терминальной операции.
users.stream()
.peek(System.out::println)
.toList();
Для реальной обработки выбирайте:
*
map() — преобразовать элементы;*
filter() — отфильтровать;*
forEach() — явно выполнить действие.Код со скрытыми побочными эффектами сложнее читать, тестировать и поддерживать.
#Java #Streams
❤4👍3🥰1
💡 Spring Boot 4: найдите устаревшие настройки до того, как они сломают запуск
При миграции на Spring Boot 4 часть старых configuration properties была переименована или удалена. Проверять большие
Gradle:
Maven:
При запуске приложения migrator:
- найдёт устаревшие ключи;
- покажет новые названия в логах;
- временно подменит свойства во время выполнения.
Важный нюанс: модуль не переписывает конфигурационные файлы. Исправьте ключи вручную, снова запустите приложение и удалите зависимость.
#SpringBoot4 #Java #Migration
При миграции на Spring Boot 4 часть старых configuration properties была переименована или удалена. Проверять большие
application.yml вручную необязательно — добавьте временный модуль:Gradle:
runtimeOnly("org.springframework.boot:spring-boot-properties-migrator")
Maven:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-properties-migrator</artifactId>
<scope>runtime</scope>
</dependency>
При запуске приложения migrator:
- найдёт устаревшие ключи;
- покажет новые названия в логах;
- временно подменит свойства во время выполнения.
Важный нюанс: модуль не переписывает конфигурационные файлы. Исправьте ключи вручную, снова запустите приложение и удалите зависимость.
properties-migrator — это временный помощник для миграции, а не библиотека, которую стоит оставлять в production.#SpringBoot4 #Java #Migration
🔥14👍3❤1
💡 Java совет: два пути могут указывать на один и тот же файл
Хотя оба пути могут вести к одному файлу.
Для проверки используйте:
- относительные пути;
- символические ссылки;
- реальное расположение файла.
Полезно при поиске дубликатов, проверке пользовательских путей и защите от повторной обработки одного файла.
Учтите: метод может обращаться к диску и выбрасывать
Доступно начиная с Java 7.
#Java #JavaTips #Files
Path.equals() сравнивает только представление пути:
Path a = Path.of("data.txt");
Path b = Path.of("./data.txt");
System.out.println(a.equals(b)); // false
Хотя оба пути могут вести к одному файлу.
Для проверки используйте:
boolean same = Files.isSameFile(a, b);
Files.isSameFile() обращается к файловой системе и учитывает:- относительные пути;
- символические ссылки;
- реальное расположение файла.
Path file = Path.of("data.txt");
Path link = Path.of("data-link.txt");
Files.isSameFile(file, link); // true, если link ведёт к data.txt
Полезно при поиске дубликатов, проверке пользовательских путей и защите от повторной обработки одного файла.
Учтите: метод может обращаться к диску и выбрасывать
IOException, поэтому это не просто строковое сравнение.Доступно начиная с Java 7.
#Java #JavaTips #Files
👍4
Причина простая: Spring Boot 4 перешёл на Servlet 6.1, а Undertow с этой версией Servlet API несовместим. Поэтому из Boot 4 удалили и поддержку embedded Undertow, и
spring-boot-starter-undertow.Если раньше было:
<artifactId>spring-boot-starter-undertow</artifactId>
теперь используйте Tomcat по умолчанию или подключайте Jetty:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jetty</artifactId>
</dependency>
Ещё один важный момент: не разворачивайте Spring Boot 4 в старом Servlet-контейнере — требуется совместимость с Servlet 6.1.
Перед миграцией Boot 3 → 4 обязательно проверьте серверную часть приложения.
#SpringBoot4 #Java #Servlet
Please open Telegram to view this post
VIEW IN TELEGRAM
👍3❤1🔥1
☕️ Java-совет: `LongAdder` вместо `AtomicLong` для горячих счётчиков
Частая ошибка в высоконагруженных системах:
Проблема: все потоки конкурируют за одну переменную.
Много потоков → больше CAS-коллизий → меньше производительность.
Для метрик лучше:
Как работает:
Идеально для:
✅ Prometheus metrics
✅ счётчиков запросов
✅ статистики JVM
✅ rate limiter
Но важно:
не гарантирует точный атомарный снимок.
Используйте:
-
-
Одна замена класса может заметно улучшить производительность под нагрузкой.
#java #jvm #performance
Частая ошибка в высоконагруженных системах:
private final AtomicLong requests = new AtomicLong();
requests.incrementAndGet();
Проблема: все потоки конкурируют за одну переменную.
Много потоков → больше CAS-коллизий → меньше производительность.
Для метрик лучше:
private final LongAdder requests = new LongAdder();
requests.increment();
Как работает:
AtomicLong:
Thread 1 ─┐
Thread 2 ─┼──> [100000]
Thread 3 ─┘
LongAdder:
Thread 1 → [10]
Thread 2 → [20]
Thread 3 → [15]
↓
sum()
LongAdder распределяет обновления между несколькими ячейками памяти, снижая конкуренцию.Идеально для:
✅ Prometheus metrics
✅ счётчиков запросов
✅ статистики JVM
✅ rate limiter
Но важно:
adder.sum()
не гарантирует точный атомарный снимок.
Используйте:
-
LongAdder → метрики и статистика;-
AtomicLong → когда нужно точное значение.Одна замена класса может заметно улучшить производительность под нагрузкой.
#java #jvm #performance
❤8👍5🔥2
⚡️ Spring Boot 4: для интеграционных тестов REST лучше переходить с
Почему это удобнее:
- fluent API в стиле
- одинаковый подход для тестов через
- проще читать цепочки
- меньше привязки к старому
Пример:
Если автоконфигурация не подтянулась автоматически, добавляем:
Если начинаете новый проект на Boot 4, TestRestTemplate уже вряд ли стоит выбирать по привычке.
#SpringBoot4 #Java #RestTestClient #Testing
TestRestTemplate на RestTestClient.Почему это удобнее:
- fluent API в стиле
RestClient;- одинаковый подход для тестов через
MockMvc и реальный HTTP-порт;- проще читать цепочки
request → response → assertions;- меньше привязки к старому
TestRestTemplate.Пример:
restTestClient.get()
.uri("/api/users/42")
.exchange()
.expectStatus().isOk()
.expectBody(User.class)
.value(user -> assertThat(user.id()).isEqualTo(42));
Если автоконфигурация не подтянулась автоматически, добавляем:
@AutoConfigureRestTestClient
Если начинаете новый проект на Boot 4, TestRestTemplate уже вряд ли стоит выбирать по привычке.
#SpringBoot4 #Java #RestTestClient #Testing
👍7❤1🔥1
💡 В Java можно избавиться от лишнего cast после
Старый вариант:
С pattern matching переменная объявляется прямо в проверке:
#Java #PatternMatching
instanceof.Старый вариант:
if (animal instanceof Dog) {
Dog d = (Dog) animal;
d.bark();
}
С pattern matching переменная объявляется прямо в проверке:
if (animal instanceof Dog d) {
d.bark();
}
d существует только там, где условие instanceof гарантированно истинно. Меньше кода, меньше лишних приведений типов и проще читать.#Java #PatternMatching
👍13❤5🔥4
☕ Java-баг, который выглядит нормально, но ломает `HashMap`
Что выведет этот код?
Многие ожидают:
Но результат:
```text
null
```
Почему?
ищет ключ через:
``` java
hashCode()
equals()
```
Когда мы сделали:
```java
map.put(key, "Ritesh");
```
ключ был:
```text
[1, 2]
```
У него был один.
Но потом:
``` java
key.add(3);
```
ключ стал:
```text
[1, 2, 3]
```
И его изменился.
уже положил объект в bucket по старому hash, а при ищет по новому.
Схема:
```text
put()
[1,2]
hash = X
↓
bucket X
key.add(3)
[1,2,3]
hash = Y
get()
ищем bucket Y
→ запись не найдена
```
Главное правило:
❌ Не изменяйте объект после того, как использовали его как ключ в .
Лучше использовать:
✅
✅
✅
✅ immutable-объекты
Например:
``` java
Map<List<Integer>, String> map = new HashMap<>();
List<Integer> key = List.of(1, 2);
map.put(key, "value");
```
Самые опасные баги вJava - те, где объект всё ещё существует, ссылка всё та же, но структура данных уже не может его нормально найти.
#java #backend #programming
Что выведет этот код?
Map<List<Integer>, String> map = new HashMap<>();
List<Integer> key = new ArrayList<>(List.of(1, 2));
map.put(key, "Ritesh");
key.add(3);
System.out.println(map.get(key));
Многие ожидают:
Ritesh
Но результат:
```text
null
```
Почему?
HashMap```
hashCode()
equals()
```
Когда мы сделали:
```
map.put(key, "Ritesh");
```
ключ был:
```text
[1, 2]
```
У него был один
hashCodeНо потом:
```
key.add(3);
```
ключ стал:
```text
[1, 2, 3]
```
И его
hashCodeHashMapget()Схема:
```text
put()
[1,2]
hash = X
↓
bucket X
key.add(3)
[1,2,3]
hash = Y
get()
ищем bucket Y
→ запись не найдена
```
Главное правило:
❌ Не изменяйте объект после того, как использовали его как ключ в
HashMapЛучше использовать:
✅
String✅
Integer✅
record✅ immutable-объекты
Например:
```
Map<List<Integer>, String> map = new HashMap<>();
List<Integer> key = List.of(1, 2);
map.put(key, "value");
```
Самые опасные баги в
👍10🔥4🤔2😱1
💡 Java: не используй `Thread.sleep()` для ожидания работы
Проблема в том, что ты угадываешь:
❌ поток может проснуться слишком рано
❌ или наоборот, ждать дольше, чем нужно
❌ polling через
Для координации потоков лучше использовать:
✅
✅
✅ другие примитивы синхронизации из
Например:
А если задача должна выполняться периодически, вместо бесконечного цикла с sleep() лучше взять
Для ожидания события и периодических задач в Java есть более точные инструменты.
#Java #Concurrency
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
❤6👍6🔥4
💡 Java: переопределяешь `equals()` - переопредели и `hashCode()`
Два объекта с одинаковым email могут быть равны по
Причина:
Правило: равные объекты обязаны иметь одинаковый `hashCode()`. При этом одинаковый хеш не гарантирует равенство - коллизии допустимы.
В примере на картинке равенство определяется по
И ещё: не меняйте поля, участвующие в `equals()` и `hashCode()`, пока объект хранится в `HashSet` или используется как ключ `HashMap` - поиск может перестать его находить.
#Java #OOP
Два объекта с одинаковым email могут быть равны по
equals(), но HashSet.contains() при этом вернёт false.Причина:
HashSet и HashMap сначала используют хеш, чтобы определить, где искать объект, а затем проверяют равенство. Если хеши различаются, до equals() дело может не дойти.Правило: равные объекты обязаны иметь одинаковый `hashCode()`. При этом одинаковый хеш не гарантирует равенство - коллизии допустимы.
В примере на картинке равенство определяется по
email, поэтому хеш тоже вычисляется по нему.И ещё: не меняйте поля, участвующие в `equals()` и `hashCode()`, пока объект хранится в `HashSet` или используется как ключ `HashMap` - поиск может перестать его находить.
#Java #OOP
👍5❤3🎉1
🍃 Spring Boot 4: проверьте зависимости Flyway и Liquibase
После обновления одной зависимости
Используйте соответствующий стартер:
* Flyway →
* Liquibase →
Для тестов, включая test slices:
*
*
Все перечисленные зависимости относятся к группе
Руководство по миграции: https://github.com/spring-projects/spring-boot/wiki/Spring-Boot-4.0-Migration-Guide#module-dependencies
#SpringBoot #Java #Flyway
После обновления одной зависимости
flyway-core или liquibase-core недостаточно для автоконфигурации миграций. В Spring Boot 4 поддержку технологий вынесли в отдельные модули.Используйте соответствующий стартер:
* Flyway →
spring-boot-starter-flyway* Liquibase →
spring-boot-starter-liquibaseДля тестов, включая test slices:
*
spring-boot-starter-flyway-test*
spring-boot-starter-liquibase-testВсе перечисленные зависимости относятся к группе
org.springframework.boot. Тестовые стартеры добавляются с областью test в Maven или через testImplementation в Gradle.Руководство по миграции: https://github.com/spring-projects/spring-boot/wiki/Spring-Boot-4.0-Migration-Guide#module-dependencies
#SpringBoot #Java #Flyway
👍6🔥3❤1
💡 Java: Checked vs Unchecked Exceptions, коротко и по делу
В Java исключения делятся на две основные категории:
- Checked - например,
- Unchecked — наследники
Пример:
Практическое правило: checked exceptions имеет смысл использовать для ситуаций, из которых вызывающий код действительно может восстановиться.
#Java #ErrorHandling
В Java исключения делятся на две основные категории:
- Checked - например,
IOException. Компилятор заставит либо обработать исключение через try/catch, либо указать throws.- Unchecked — наследники
RuntimeException. Обязательной обработки на этапе компиляции нет.Пример:
IOException → checked NullPointerException → unchecked IllegalArgumentException → uncheckedПрактическое правило: checked exceptions имеет смысл использовать для ситуаций, из которых вызывающий код действительно может восстановиться.
#Java #ErrorHandling
👍4❤1
🚀 Spring Boot 4 получил отдельный starter для
Теперь imperative HTTP-клиент можно подключать через:
Для реактивного
А настройки HTTP-клиентов собраны под единым префиксом:
То есть конфигурация стала понятнее, а выбор между imperative и reactive подходом — более явным.
#SpringBoot4 #RestClient #Java
RestClientТеперь imperative HTTP-клиент можно подключать через:
spring-boot-starter-restclientДля реактивного
WebClient используется:spring-boot-starter-webclientА настройки HTTP-клиентов собраны под единым префиксом:
spring.http.clients.*То есть конфигурация стала понятнее, а выбор между imperative и reactive подходом — более явным.
#SpringBoot4 #RestClient #Java
❤7👍4🔥4🎉1
⚡️Продвинутый Java-совет: используйте `ClassValue`
Если нужно кешировать метаданные отдельно для каждого класса, не обязательно создавать
Полезно для reflection, сериализации, мапперов и собственных фреймворков.
#Java #JavaTips #Backend
Если нужно кешировать метаданные отдельно для каждого класса, не обязательно создавать
ConcurrentHashMap<Class<?>, Metadata>.
private static final ClassValue<Metadata> CACHE =
new ClassValue<>() {
@Override
protected Metadata computeValue(Class<?> type) {
return scan(type);
}
};
Metadata metadata = CACHE.get(MyClass.class);
ClassValue лениво вычисляет значение, безопасно работает при конкуренции потоков и хранит отдельный результат для каждого Class. Сбросить кеш можно через remove().Полезно для reflection, сериализации, мапперов и собственных фреймворков.
#Java #JavaTips #Backend
🔥7❤3🎉3👍1
🍃 Не раскладывайте весь Spring-проект по папкам controller, service и repository.
В новом This Week in Spring Джош Лонг советует группировать код по функциональности, используя подход Spring Modulith. Например,
Ещё в подборке:
• JobRunr 9: поиск упавшего шага и задач, вызывающих замедление, остановка выполнения без потери работы.
• Spring AI: разбор агентных паттернов и вызова инструментов от руководителя проекта Кристиана Цолова.
• Progen CLI: видео о генерации каркаса приложения.
• IntelliJ IDEA: обзор новых возможностей.
• A Bootiful Podcast: разговор с создателем Jenkins Косуке Кавагути.
https://spring.io/blog/2026/10/13/this-week-in-spring-october-13th-2026
#Java #SpringBoot
В новом This Week in Spring Джош Лонг советует группировать код по функциональности, используя подход Spring Modulith. Например,
orders, payments, users, внутри которых находятся относящиеся к ним слои.Ещё в подборке:
• JobRunr 9: поиск упавшего шага и задач, вызывающих замедление, остановка выполнения без потери работы.
• Spring AI: разбор агентных паттернов и вызова инструментов от руководителя проекта Кристиана Цолова.
• Progen CLI: видео о генерации каркаса приложения.
• IntelliJ IDEA: обзор новых возможностей.
• A Bootiful Podcast: разговор с создателем Jenkins Косуке Кавагути.
https://spring.io/blog/2026/10/13/this-week-in-spring-october-13th-2026
#Java #SpringBoot
🔥7❤5🎉2