Modern Java 11-21

30.06.2026 | Muallif: Jaxongir a.k.a | Kategoriya: Java tili | 25 daqiqa o'qish

Modern Java - Java 8'dan keyingi muhim yangiliklar. Middle developer faqat Java 8 Stream bilan qolib ketmasligi kerak. Hozir real loyihalarda Java 11, 17 va 21 ko‘p ishlatiladi.

Bu bo‘limga quyidagilar kiradi: Records, Sealed classes, Pattern matching, Text blocks, var, Virtual threads.


1. Java 11-21 nima uchun muhim?

Java 8 uzoq vaqt standart bo‘lib keldi. Lekin Java 11, 17, 21 versiyalarida kodni:

kamroq boilerplate
o‘qilishi oson
type-safe
immutabilityga yaqin
concurrency uchun qulay
productionga mos

qilish uchun ko‘p imkoniyatlar qo‘shildi.

Backend developer uchun eng muhimlari:

Feature

Qayerda kerak

var

Local variablelarda qisqa yozish

Text Blocks

SQL, JSON, HTML yozish

Records

DTO, response, immutable data object

Pattern Matching

instanceof, switch bilan toza kod

Sealed Classes

Cheklangan inheritance

Virtual Threads

Ko‘p blocking I/O requestlar


2. var - Local Type Inference

var Java 10'da kelgan. U local variable type’ni compilerga aniqlatadi.

Oldin:

String name = "Ali";
List<String> users = new ArrayList<>();

var bilan:

var name = "Ali";
var users = new ArrayList<String>();

Compiler o‘zi tushunadi:

name -> String
users -> ArrayList<String>

var qayerda yaxshi?

Agar type aniq ko‘rinib tursa:

var user = new User("Ali", "ali@example.com");
var amount = BigDecimal.valueOf(100_000);
var formatter = DateTimeFormatter.ISO_LOCAL_DATE;

Bu yaxshi, chunki o‘qishda muammo yo‘q.


var qayerda yomon?

Agar method nomidan type bilinmasa:

var result = service.process(data);
var value = manager.get();
var response = client.call();

Bu joyda var o‘qishni qiyinlashtiradi. Developer result nimani anglatishini IDE’siz bilmaydi.

Yaxshiroq:

PaymentResponse response = paymentClient.createPayment(request);

var fieldlarda ishlaydimi?

Yo‘q.

private var name = "Ali"; // xato

var faqat local variable uchun ishlaydi:

public void test() {
    var name = "Ali";
}

var bilan null

Bu ishlamaydi:

var user = null; // compiler type’ni bilmaydi

To‘g‘ri:

User user = null;

3. Text Blocks

Text Blocks Java 15'da stable bo‘lgan. Ko‘p qatorli stringlarni qulay yozish uchun.

Oldin SQL yozish noqulay edi:

String sql = "SELECT * " +
        "FROM users " +
        "WHERE status = 'ACTIVE'";

Text block bilan:

String sql = """
        SELECT *
        FROM users
        WHERE status = 'ACTIVE'
        """;

Ancha o‘qilishi oson.


JSON uchun

Oldin:

String json = "{ \"name\": \"Ali\", \"age\": 25 }";

Text block bilan:

String json = """
        {
          "name": "Ali",
          "age": 25
        }
        """;

Testlarda juda foydali:

mockMvc.perform(post("/users")
        .contentType(MediaType.APPLICATION_JSON)
        .content("""
            {
              "name": "Ali",
              "email": "ali@example.com"
            }
            """))
        .andExpect(status().isOk());

JPQL yoki native query uchun

@Query("""
    select u
    from User u
    where u.status = :status
    order by u.createdAt desc
    """)
List<User> findByStatus(UserStatus status);

Bu + bilan string ulashdan ancha toza.


4. Records

Record - data tashish uchun qisqa, immutable’ga yaqin class.

Oddiy DTO yozsak:

public class UserDto {
    private final Long id;
    private final String name;
    private final String email;

    public UserDto(Long id, String name, String email) {
        this.id = id;
        this.name = name;
        this.email = email;
    }

    public Long getId() {
        return id;
    }

    public String getName() {
        return name;
    }

    public String getEmail() {
        return email;
    }
}

Record bilan:

public record UserDto(
        Long id,
        String name,
        String email
) {
}

Java avtomatik yaratadi:

constructor
accessor methods
equals()
hashCode()
toString()

Record accessor methodlari

Record’da getter nomi boshqacha:

UserDto dto = new UserDto(1L, "Ali", "ali@example.com");

System.out.println(dto.id());
System.out.println(dto.name());
System.out.println(dto.email());

getId() emas, id().


Record qayerda yaxshi?

Record quyidagilar uchun juda qulay:

DTO
API response
API request
projection
event object
command object
test data
small immutable value object

Masalan:

public record CreateUserRequest(
        String name,
        String email,
        String phone
) {
}
public record OrderCreatedEvent(
        Long orderId,
        Long userId
) {
}
public record UserListItemDto(
        Long id,
        String fullName
) {
}

Record validation bilan

public record RegisterRequest(
        @NotBlank String name,
        @Email String email,
        @Pattern(regexp = "^\\+998\\d{9}$") String phone
) {
}

Controller:

@PostMapping("/register")
public ResponseEntity<UserDto> register(@Valid @RequestBody RegisterRequest request) {
    return ResponseEntity.ok(userService.register(request));
}

Record ichida custom constructor

Agar qoida qo‘shmoqchi bo‘lsang:

public record Money(BigDecimal amount, String currency) {

    public Money {
        if (amount == null || amount.compareTo(BigDecimal.ZERO) < 0) {
            throw new IllegalArgumentException("Amount manfiy bo‘lmasin");
        }

        if (currency == null || currency.isBlank()) {
            throw new IllegalArgumentException("Currency bo‘sh bo‘lmasin");
        }
    }
}

Bu compact constructor deyiladi.


Record entity bo‘la oladimi?

Texnik tomondan record immutable. JPA/Hibernate entity uchun no-args constructor, proxy, mutable state kerak bo‘lishi mumkin. Shuning uchun odatda:

Record -> DTO uchun yaxshi
Class -> Entity uchun yaxshi

Yomon:

@Entity
public record User(Long id, String name) {
}

Yaxshi:

@Entity
public class User {
    // entity
}

public record UserDto(Long id, String name) {
    // DTO
}

5. Pattern Matching for instanceof

Oldin:

if (obj instanceof String) {
    String text = (String) obj;
    System.out.println(text.toUpperCase());
}

Yangi usul:

if (obj instanceof String text) {
    System.out.println(text.toUpperCase());
}

Bu yerda Java o‘zi cast qiladi.


Real misol

public String formatValue(Object value) {
    if (value instanceof String text) {
        return text.trim().toUpperCase();
    }

    if (value instanceof Integer number) {
        return "Number: " + number;
    }

    if (value instanceof BigDecimal amount) {
        return "Amount: " + amount;
    }

    return "Unknown";
}

Kod qisqaroq va xavfsizroq.


6. Pattern Matching for switch

Java 21'da pattern matching for switch stable bo‘ldi.

Oldingi usul:

public String describe(Object obj) {
    if (obj instanceof String s) {
        return "String: " + s;
    } else if (obj instanceof Integer i) {
        return "Integer: " + i;
    } else if (obj instanceof Long l) {
        return "Long: " + l;
    }

    return "Unknown";
}

Yangi usul:

public String describe(Object obj) {
    return switch (obj) {
        case String s -> "String: " + s;
        case Integer i -> "Integer: " + i;
        case Long l -> "Long: " + l;
        case null -> "Null";
        default -> "Unknown";
    };
}

Bu ayniqsa sealed classlar bilan kuchli ishlaydi.


7. Switch Expression

Java 14'da switch expression stable bo‘lgan.

Oldin:

String result;

switch (status) {
    case NEW:
        result = "Yangi";
        break;
    case PAID:
        result = "To‘langan";
        break;
    default:
        result = "Noma’lum";
}

Yangi usul:

String result = switch (status) {
    case NEW -> "Yangi";
    case PAID -> "To‘langan";
    case CANCELLED -> "Bekor qilingan";
};

Agar enum barcha qiymatlar qamrab olingan bo‘lsa, default shart bo‘lmasligi mumkin.


Real misol

public BigDecimal calculateFee(PaymentType type, BigDecimal amount) {
    return switch (type) {
        case PAYME -> amount.multiply(BigDecimal.valueOf(0.01));
        case CLICK -> amount.multiply(BigDecimal.valueOf(0.012));
        case CASH -> BigDecimal.ZERO;
    };
}

Bu if/elsedan o‘qilishi yaxshi.


8. Sealed Classes

Sealed class/interface - qaysi classlar extend/implement qila olishini cheklaydi.

Masalan, payment result faqat 3 xil bo‘lishi mumkin:

Success
Failed
Pending

Sealed interface:

public sealed interface PaymentResult
        permits PaymentSuccess, PaymentFailed, PaymentPending {
}

Implementationlar:

public record PaymentSuccess(String transactionId)
        implements PaymentResult {
}
public record PaymentFailed(String reason)
        implements PaymentResult {
}
public record PaymentPending(String message)
        implements PaymentResult {
}

Endi boshqa class PaymentResultni implement qila olmaydi, agar permits ichida bo‘lmasa.


Sealed + switch

public String handle(PaymentResult result) {
    return switch (result) {
        case PaymentSuccess success -> "Success: " + success.transactionId();
        case PaymentFailed failed -> "Failed: " + failed.reason();
        case PaymentPending pending -> "Pending: " + pending.message();
    };
}

Bu yerda compiler hamma holat qamrab olinganini tekshiradi.

Agar keyin yangi type qo‘shsak:

public record PaymentExpired(String message) implements PaymentResult {
}

switch sizni ogohlantiradi: yangi case qo‘shish kerak.


Sealed qachon kerak?

Quyidagi holatlarda:

natija turlari cheklangan bo‘lsa
domain state aniq bo‘lsa
command/event turlari nazorat qilinsa
error model aniq bo‘lsa
compiler yordamida exhaustive checking kerak bo‘lsa

Masalan:

public sealed interface OrderState
        permits NewOrder, PaidOrder, CancelledOrder, DeliveredOrder {
}

Yoki:

public sealed interface ApiResult
        permits SuccessResult, ValidationErrorResult, ServerErrorResult {
}

9. Helpful NullPointerException

Java 14'dan boshlab NPE xabarlari ancha tushunarli bo‘ldi.

Oldin:

NullPointerException

Yangi xabarlar aniqroq:

Cannot invoke "User.getName()" because "user" is null

Masalan:

Order order = null;
System.out.println(order.getUser().getName());

Xato qaysi object null ekanini ko‘rsatadi.

Bu debuggingda juda foydali.


10. Optional yaxshiroq ishlatish

Optional Java 8'da kelgan, lekin modern Java’da ham to‘g‘ri ishlatish muhim.

Yomon:

Optional<User> optional = userRepository.findById(id);

if (optional.isPresent()) {
    return optional.get();
}

throw new RuntimeException("User topilmadi");

Yaxshi:

User user = userRepository.findById(id)
        .orElseThrow(() -> new RuntimeException("User topilmadi"));

Optional field sifatida ishlatilmasin

Yomon:

public class User {
    private Optional<String> phone;
}

Yaxshi:

public class User {
    private String phone;
}

Optional asosan method return type uchun ishlatiladi.


11. Collection factory methods

Java 9'dan boshlab immutable collection yaratish osonlashdi.

Oldin:

List<String> roles = Arrays.asList("ADMIN", "USER");

Yangi:

List<String> roles = List.of("ADMIN", "USER");
Set<String> permissions = Set.of("READ", "WRITE");
Map<String, Integer> limits = Map.of(
        "daily", 100,
        "monthly", 1000
);

Bu collectionlar immutable:

roles.add("MANAGER"); // UnsupportedOperationException

Qayerda yaxshi?

private static final List<String> PUBLIC_URLS = List.of(
        "/api/auth/login",
        "/api/auth/register",
        "/actuator/health"
);

Yoki test data:

var items = List.of(
        new OrderItem("Laptop", 1),
        new OrderItem("Mouse", 2)
);

12. Stream API yangiliklari

Java 16'dan toList() qo‘shildi.

Oldin:

List<String> names = users.stream()
        .map(User::getName)
        .collect(Collectors.toList());

Yangi:

List<String> names = users.stream()
        .map(User::getName)
        .toList();

Lekin farq bor: stream().toList() qaytargan list unmodifiable bo‘lishi mumkin.

names.add("Vali"); // UnsupportedOperationException bo‘lishi mumkin

Agar mutable list kerak bo‘lsa:

List<String> names = users.stream()
        .map(User::getName)
        .collect(Collectors.toCollection(ArrayList::new));

13. String API yangiliklari

Java 11'da foydali methodlar bor:

" ".isBlank();          // true
" Ali ".strip();        // "Ali"
"Java\nSpring".lines(); // Stream<String>
"ha".repeat(3);         // "hahaha"

Misol:

public boolean isValidName(String name) {
    return name != null && !name.isBlank();
}

isBlank() trim().isEmpty()dan qulayroq.


14. HttpClient

Java 11'da built-in HTTP client bor.

Oldin ko‘pincha Apache HttpClient yoki OkHttp ishlatilardi. Hozir oddiy holatlar uchun Java’ning o‘zidagi HttpClient ham yetarli.

HttpClient client = HttpClient.newHttpClient();

HttpRequest request = HttpRequest.newBuilder()
        .uri(URI.create("https://api.example.com/users"))
        .GET()
        .build();

HttpResponse<String> response = client.send(
        request,
        HttpResponse.BodyHandlers.ofString()
);

System.out.println(response.body());

Async:

client.sendAsync(request, HttpResponse.BodyHandlers.ofString())
        .thenApply(HttpResponse::body)
        .thenAccept(System.out::println);

Spring loyihalarda esa ko‘pincha:

RestClient
WebClient
Feign

ishlatiladi. Lekin Java HttpClientni bilish foydali.


15. Virtual Threads

Virtual threads Java 21'da eng muhim yangiliklardan biri.

Oddiy platform thread OS thread bilan bog‘langan:

Java Thread -> OS Thread

Virtual thread esa ancha yengil:

Ko‘p virtual thread -> kamroq platform threadlarda boshqariladi

Oddiy thread pool

ExecutorService executor = Executors.newFixedThreadPool(100);

executor.submit(() -> {
    processRequest();
});

Thread soni cheklangan. Agar blocking I/O ko‘p bo‘lsa, threadlar kutib qoladi.


Virtual thread executor

try (var executor = Executors.newVirtualThreadPerTaskExecutor()) {
    executor.submit(() -> {
        processRequest();
    });
}

Har task uchun virtual thread yaratiladi. Ular juda yengil.


Virtual thread qayerda foydali?

Ayniqsa blocking I/O’da:

database call
external HTTP API call
file read/write
queue consumer
microservice call
REST API backend

Masalan, bitta request ichida DB va tashqi API chaqiriladi:

public UserProfile getProfile(Long userId) {
    User user = userRepository.findById(userId).orElseThrow();
    PaymentInfo payment = paymentClient.getPaymentInfo(userId);
    return new UserProfile(user, payment);
}

Bu blocking kod. Virtual threads bilan ko‘p requestni kamroq resource bilan boshqarish mumkin.


Virtual thread CPU-heavy ish uchun yechimmi?

Yo‘q.

CPU-heavy ishlar:

image processing
video encoding
cryptography
large calculation
compression
ML inference

Bunday joylarda CPU core limit bo‘ladi. Virtual thread CPU’ni ko‘paytirib bermaydi.


Spring Boot’da virtual threads

Spring Boot 3.2+ da virtual threads yoqish mumkin:

spring:
  threads:
    virtual:
      enabled: true

Bu servlet request handling va ayrim task executionlarda virtual threadlardan foydalanishga yordam beradi.

Lekin production’da yoqishdan oldin tekshirish kerak:

JDBC driver mosmi?
librarylar ThreadLocalga qattiq bog‘lanmaganmi?
synchronized bloklarda pinning yo‘qmi?
monitoring sozlanganmi?
load test qilinganmi?

16. Virtual threads va ThreadLocal

Virtual threadlar bilan ThreadLocal ishlaydi, lekin ehtiyot bo‘lish kerak. Juda ko‘p virtual thread bo‘lsa, har birida katta object saqlash memory muammosi qilishi mumkin.

Yomon:

private static final ThreadLocal<byte[]> buffer =
        ThreadLocal.withInitial(() -> new byte[1024 * 1024]);

Agar juda ko‘p virtual thread bo‘lsa, memory katta ketadi.


17. Structured Concurrency

Java 21'da Structured Concurrency preview sifatida bor. G‘oyasi: parallel tasklarni tartibli boshqarish.

Masalan:

User data olish
Order data olish
Payment data olish

Bular bir request doirasida parallel bajariladi. Agar bittasi fail bo‘lsa, qolganlarini cancel qilish kerak bo‘lishi mumkin.

Konseptual ko‘rinish:

try (var scope = new StructuredTaskScope.ShutdownOnFailure()) {
    var user = scope.fork(() -> userClient.getUser(id));
    var orders = scope.fork(() -> orderClient.getOrders(id));

    scope.join();
    scope.throwIfFailed();

    return new Profile(user.get(), orders.get());
}

Bu hali preview bo‘lgani uchun production’da ehtiyotkorlik bilan qaraladi.


18. Sequenced Collections

Java 21'da sequenced collections qo‘shildi.

Oldin List, LinkedHashSet, SortedSet kabi collectionlarda tartib bor edi, lekin umumiy interface yetishmasdi.

Endi:

SequencedCollection<String> names = new ArrayList<>();

names.addFirst("Ali");
names.addLast("Vali");

String first = names.getFirst();
String last = names.getLast();

Bu hamma loyihada darrov ishlatiladigan feature emas, lekin Java 21 API’sini bilish uchun foydali.


19. Modern Java real Spring Boot misol

Keling, user registration uchun modern Java uslubida yozamiz.

Request record

public record RegisterRequest(
        @NotBlank String name,
        @Email String email,
        @Pattern(regexp = "^\\+998\\d{9}$") String phone
) {
}

Response record

public record UserDto(
        Long id,
        String name,
        String email,
        String phone
) {
    public static UserDto from(User user) {
        return new UserDto(
                user.getId(),
                user.getName(),
                user.getEmail(),
                user.getPhone()
        );
    }
}

Service

@Service
public class UserService {

    private final UserRepository userRepository;

    public UserService(UserRepository userRepository) {
        this.userRepository = userRepository;
    }

    @Transactional
    public UserDto register(RegisterRequest request) {
        userRepository.findByEmail(request.email())
                .ifPresent(user -> {
                    throw new IllegalStateException("Email already exists");
                });

        var user = new User(
                request.name(),
                request.email(),
                request.phone()
        );

        var saved = userRepository.save(user);

        return UserDto.from(saved);
    }
}

Controller

@RestController
@RequestMapping("/api/users")
public class UserController {

    private final UserService userService;

    public UserController(UserService userService) {
        this.userService = userService;
    }

    @PostMapping("/register")
    public ResponseEntity<UserDto> register(@Valid @RequestBody RegisterRequest request) {
        var response = userService.register(request);
        return ResponseEntity.status(HttpStatus.CREATED).body(response);
    }
}

Bu yerda ishlatilgan modern Java:

record
var
Optional
validation annotations
static factory mapping

20. Real misol: sealed error model

API errorlarni tartibli model qilish:

public sealed interface AppError
        permits ValidationError, NotFoundError, ConflictError {
}
public record ValidationError(
        List<FieldErrorDto> fields
) implements AppError {
}
public record NotFoundError(
        String resource,
        Long id
) implements AppError {
}
public record ConflictError(
        String message
) implements AppError {
}

Handler:

public ErrorResponse toResponse(AppError error) {
    return switch (error) {
        case ValidationError validation -> new ErrorResponse(
                "VALIDATION_ERROR",
                validation.fields().toString()
        );
        case NotFoundError notFound -> new ErrorResponse(
                "NOT_FOUND",
                notFound.resource() + " not found: " + notFound.id()
        );
        case ConflictError conflict -> new ErrorResponse(
                "CONFLICT",
                conflict.message()
        );
    };
}

Compiler barcha error typelar qamrab olinganini tekshiradi.


21. Java 11, 17, 21 bo‘yicha qisqa ajratish

Java 11

Muhimlari:

HttpClient
String.isBlank()
String.strip()
String.lines()
String.repeat()
var lambda parameters
Files.readString/writeString

Misol:

String content = Files.readString(Path.of("data.txt"));
Files.writeString(Path.of("out.txt"), content);

Java 17

Ko‘p enterprise loyihalar uchun kuchli LTS versiya.

Muhimlari:

Records
Sealed classes
Pattern matching for instanceof
Text blocks
Switch expressions
Helpful NPE

Java 17 hozir ko‘p Spring Boot loyihalarda minimum sifatida ishlatiladi.


Java 21

Yangi kuchli LTS.

Muhimlari:

Virtual threads
Pattern matching for switch
Sequenced collections
Record patterns preview
Structured concurrency preview
Scoped values preview

Backend uchun eng kattasi: Virtual threads.


22. Qaysi feature’ni qachon ishlatish kerak?

Holat

Modern Java yechim

DTO kerak

record

Ko‘p qatorli SQL/JSON kerak

Text Block

Object type tekshirish kerak

Pattern matching

Enum asosida qiymat qaytarish

Switch expression

Natija turlari cheklangan

Sealed interface/class

Local type juda aniq

var

Blocking I/O ko‘p

Virtual threads

Immutable constant list

List.of, Set.of, Map.of


23. Noto‘g‘ri ishlatishdan ehtiyot bo‘lish

varni hamma joyga tiqish

Yomon:

var x = service.doSomething();

Yaxshi:

PaymentResponse response = paymentService.createPayment(request);

Record’ni entity qilish

Yomon:

@Entity
public record Product(Long id, String name) {
}

Yaxshi:

@Entity
public class Product {
}
public record ProductDto(Long id, String name) {
}

Virtual threadni hamma muammoning yechimi deb o‘ylash

Virtual threads:

blocking I/O uchun samarali
CPU-heavy ish uchun sehr emas

Pattern matching bilan murakkab business logicni tiqish

Yomon:

return switch (obj) {
    case A a -> // 40 qator logic
    case B b -> // 50 qator logic
};

Yaxshi:

return switch (command) {
    case CreateOrderCommand c -> createOrderHandler.handle(c);
    case CancelOrderCommand c -> cancelOrderHandler.handle(c);
};

24. Interview savollari

var nima?

Local variable type inference. Compiler type’ni o‘zi aniqlaydi. Field yoki method parameter uchun ishlamaydi.

Record nima?

Immutable’ga yaqin data carrier class. Constructor, accessor, equals, hashCode, toString avtomatik yaratiladi.

Record entity sifatida ishlatiladimi?

Odatda yo‘q. JPA entity uchun class ishlatish yaxshiroq. Record DTO uchun qulay.

Text block nima?

Ko‘p qatorli string yozish imkoniyati. SQL, JSON, HTML, test requestlarda qulay.

Pattern matching nima beradi?

Type check va castni soddalashtiradi.

Sealed class nima?

Qaysi classlar extend/implement qila olishini cheklaydi.

Virtual thread nima?

Yengil thread modeli. Ko‘p blocking I/O tasklarni kamroq resource bilan bajarishga yordam beradi.

Virtual thread CPU-heavy taskni tezlashtiradimi?

Yo‘q. CPU-heavy ish CPU core bilan cheklanadi.

stream().toList() va Collectors.toList() farqi?

stream().toList() unmodifiable list qaytarishi mumkin. Mutable list kerak bo‘lsa, Collectors.toCollection(ArrayList::new) ishlatish mumkin.

Java 17 va Java 21 orasida backend uchun eng katta farq nima?

Java 21'da virtual threads va pattern matching for switch stable bo‘lgani katta farq.


25. Middle Developer checklist

Modern Java 11-21 bo‘yicha siz quyidagilarni bilishingiz kerak:

var qachon ishlatish/qachon ishlatmaslik
Text Blocks bilan SQL/JSON yozish
Record bilan DTO yozish
Record constructor validation
Pattern matching for instanceof
Switch expression
Pattern matching for switch
Sealed classes/interfaces
Optional to‘g‘ri ishlatish
List.of/Set.of/Map.of
Stream.toList farqi
String API yangiliklari
HttpClient basics
Virtual threads
Virtual threads qachon foydali/qachon emas
Spring Boot’da virtual threads config

Qisqa xulosa

Modern Java 11-21 Middle developerga kodni ancha toza va productionga mos yozishga yordam beradi.

Eng ko‘p ishlatiladiganlari:

record
text block
switch expression
pattern matching
var
List.of / Set.of / Map.of
virtual threads

Junior shunday yozishi mumkin:

public class UserDto {
    private Long id;
    private String name;
    private String email;

    // constructor, getter, setter...
}

Middle modern Java bilan shunday yozadi:

public record UserDto(
        Long id,
        String name,
        String email
) {
}