Java не умирает — она перерождается
Каждый год кто-то пишет «Java мертва». И каждый год Java доказывает обратное. В 2025 году язык, которому уже почти 30 лет, получает сразу несколько фундаментальных обновлений. Если вы пишете на Java, но последний раз смотрели changelog год назад — вы уже отстали. Давайте разберем, что конкретно изменится и как эти изменения повлияют на ваш код.
Я не буду грузить вас сухими спецификациями. Вместо этого — реальные примеры, которые вы сможете запустить уже сегодня (ну, почти). Поехали.
Виртуальные потоки (Project Loom) — конец эпохи блокировок
Это, пожалуй, самое громкое изменение. Виртуальные потоки (virtual threads) — это легковесные потоки, которые позволяют писать асинхронный код в синхронном стиле. В 2025 году они наконец-то становятся стабильными и рекомендуемыми для продакшена.
Раньше вы писали что-то вроде:
ExecutorService executor = Executors.newFixedThreadPool(10);
executor.submit(() -> {
// блокирующий вызов
String result = restTemplate.getForObject(url, String.class);
return result;
});
Проблема в том, что каждый поток — это тяжелый объект. 10 потоков — ок, 10 000 — уже проблемы. С виртуальными потоками вы можете запускать миллионы потоков без оглядки на память:
try (var executor = Executors.newVirtualThreadPerTaskExecutor()) {
for (int i = 0; i < 100_000; i++) {
executor.submit(() -> {
// теперь это легковесно
String result = restTemplate.getForObject(url, String.class);
return result;
});
}
}
Ключевое отличие: виртуальные потоки не привязаны к потокам ОС. Когда поток блокируется (например, на I/O), JVM просто переключает его на другой виртуальный поток. Никаких накладных расходов.
Что это значит для вас? Забудьте про сложные асинхронные фреймворки типа CompletableFuture и реактивные библиотеки. В 2025 году стандартный синхронный код на виртуальных потоках будет и быстрее, и проще.
Как мигрировать
Замените newFixedThreadPool на newVirtualThreadPerTaskExecutor. Если используете Spring Boot — обновитесь до версии 3.2+, там уже есть поддержка. Только убедитесь, что ваши библиотеки не используют synchronized в критических секциях — это единственный момент, где виртуальные потоки могут «застрять».
Pattern Matching и Switch — код становится короче
В 2025 году pattern matching добрался до switch и instanceof. Теперь вместо кучи вложенных if-else можно писать лаконично и читаемо.
Было (старый стиль):
if (obj instanceof String) {
String s = (String) obj;
System.out.println(s.length());
} else if (obj instanceof Integer) {
Integer i = (Integer) obj;
System.out.println(i * 2);
}
Стало (Java 21+):
switch (obj) {
case String s -> System.out.println(s.length());
case Integer i -> System.out.println(i * 2);
case null -> System.out.println("null");
default -> System.out.println("unknown");
}
Обратите внимание на case null — это отдельное улучшение. Раньше switch выбрасывал NullPointerException при передаче null. Теперь можно обработать это явно.
Еще крутая фича — record patterns. Если у вас есть record:
record Point(int x, int y) {}
То можно писать:
if (obj instanceof Point(int x, int y)) {
System.out.println("x=" + x + ", y=" + y);
}
Без лишнего приведения типов. В 2025 году это будет мейнстримом — используйте, чтобы код стал чище.
Сборка мусора: ZGC и Generational GC
В 2025 году сборка мусора стала еще быстрее. ZGC (Z Garbage Collector) теперь включен по умолчанию для большинства приложений. Его главное преимущество — паузы менее 1 миллисекунды, независимо от размера кучи.
Раньше вы могли испытывать «фризы» приложения на несколько секунд во время GC. С ZGC это в прошлом. Для веб-серверов и микросервисов это означает стабильный latency без тюнинга.
Но есть нюанс: ZGC потребляет больше CPU. Если ваше приложение не чувствительно к задержкам (например, batch-обработка), можно оставить G1GC. Но для real-time систем — ZGC ваш выбор.
Настройка простая:
-XX:+UseZGC
Или, если используете Java 22+:
-XX:+UseZGC -XX:ZCollectionInterval=60
Также появился Generational ZGC — гибрид, который разделяет молодое и старое поколения. Он чуть сложнее, но дает лучшую производительность для приложений с короткоживущими объектами.
Foreign Function & Memory API — прощайте, JNI
В 2025 году Foreign Function & Memory API (FFM) становится стабильным. Это замена старому и неудобному JNI (Java Native Interface). Теперь вызывать C-библиотеки можно без нативного кода.
Пример: вызов функции printf из libc:
import java.lang.foreign.*;
import java.lang.invoke.*;
public class HelloNative {
public static void main(String[] args) throws Throwable {
Linker linker = Linker.nativeLinker();
SymbolLookup stdlib = linker.defaultLookup();
MethodHandle printf = linker.downcallHandle(
stdlib.find("printf").get(),
FunctionDescriptor.of(ValueLayout.JAVA_INT, ValueLayout.ADDRESS)
);
try (Arena arena = Arena.ofConfined()) {
MemorySegment str = arena.allocateUtf8String("Hello from Java 2025!\n");
printf.invoke(str);
}
}
}
Да, выглядит громоздко, но это в разы проще, чем писать .h файлы и компилировать JNI. Для работы с си-библиотеками (например, для криптографии или работы с оборудованием) это спасение.
Совет: не используйте FFM для простых задач — для них хватит стандартной библиотеки. Но если вам нужно вызвать нативную функцию, теперь это делается без боли.
String Templates — строки станут умнее
В 2025 году в Java появляются string templates (в预览 версии с Java 21, стабильные в 23+). Это как f-strings в Python, только типобезопасные.
Было:
String message = "User " + user.getName() + " has " + user.getPoints() + " points";
Стало:
String message = STR."User \{user.getName()} has \{user.getPoints()} points";
Префикс STR — это процессор шаблонов. Можно создавать свои процессоры для безопасной интерполяции SQL, JSON и т.д. Например: