Опирается на правила:
JS-6.1…JS-6.8иJS-6.X1…JS-6.X2из Java Style Guide → раздел 6. Lombok.
Важно знать
@RequiredArgsConstructor— всегда для классов сprivate finalполями (Spring beans, services, exceptions, value-objects).@Slf4j— вместо ручногоLoggerFactory.getLogger.@Getterна custom exceptions и value-objects (не record).- Lombok НЕ на records — record уже даёт ctor, accessors, equals, hashCode, toString.
@DataЗАПРЕЩЁН в production — mutable setters + equals/hashCode по всем полям.@Builderточечно — сложные DTO 5+ полей; запрещён на aggregate root.@Getter/@Setterдля mutable классов где record неприменим (jOOQ POJO, JPA entity).@AllArgsConstructorзапрещён для DI-классов — non-final поля попадают в ctor.
Lombok устраняет boilerplate без изменения семантики кода. UCP формулирует обязательные defaults: каждый класс с final полями использует @RequiredArgsConstructor, каждый класс с логированием — @Slf4j. Не «можно использовать», а «обязан использовать». Запреты — на @Data и неаккуратные @AllArgsConstructor.
@RequiredArgsConstructor — всегда
JS-6.1: универсальное правило.
// Spring bean
@Component
@RequiredArgsConstructor
public class CreateOrderHandler implements UseCaseHandler<CreateOrderCommand, Order> {
private final OrderRepository orderRepository;
private final DateTimeService dateTimeService;
private final UuidGenerator uuidGenerator;
}
// Не Spring bean — те же правила
@RequiredArgsConstructor
public class OrderTotalCalculator {
private final TaxService taxService;
private final DiscountPolicy discountPolicy;
public Money calculate(List<OrderItem> items) { ... }
}
// Custom exception с payload
@Getter
@RequiredArgsConstructor
public class InvalidStateTransitionException extends RuntimeException {
private final ProductStatus from;
private final ProductStatus to;
}
Lombok генерирует:
public CreateOrderHandler(OrderRepository orderRepository, DateTimeService dateTimeService, UuidGenerator uuidGenerator) {
this.orderRepository = orderRepository;
this.dateTimeService = dateTimeService;
this.uuidGenerator = uuidGenerator;
}
Применяется ко всем private final полям. Optional fields (не final, не required) — игнорируются.
Когда явный ctor оправдан
- Валидация аргументов —
Objects.requireNonNull,if (x < 0) throw .... super(...)с не-стандартными аргументами.- Factory-method:
Order.draft(...),Order.fromPersistence(...)— это уже named-constructor. - No-arg ctor для JPA/Jackson —
@NoArgsConstructor(access = PROTECTED)+@Getter+@Setter.
public CreateOrderHandler(OrderRepository orderRepository, DateTimeService dateTimeService) {
this.orderRepository = Objects.requireNonNull(orderRepository, "orderRepository");
this.dateTimeService = Objects.requireNonNull(dateTimeService, "dateTimeService");
}
С Spring @Autowired и не-null validation — обычно не нужно (Spring сам бросает NoSuchBeanDefinitionException).
@Slf4j
JS-6.2: вместо ручного логгера.
// ✓
@Slf4j
@Component
public class OrderService {
public void process(Order order) {
log.info("Processing order: orderId={}", order.id());
}
}
// ✗ — boilerplate
@Component
public class OrderService {
private static final Logger log = LoggerFactory.getLogger(OrderService.class);
public void process(Order order) {
log.info("Processing order: orderId={}", order.id());
}
}
@Slf4j генерирует private static final org.slf4j.Logger log = LoggerFactory.getLogger(ClassName.class). Без boilerplate, без ошибки в class-аргументе (getLogger(OtherClass.class)).
См. также Observability → logging.
@Getter
JS-6.3: для exception payload и value-objects.
@Getter
@RequiredArgsConstructor
public class InvalidStateTransitionException extends RuntimeException {
private final ProductStatus from;
private final ProductStatus to;
@Override
public String getMessage() {
return "Invalid transition: " + from + " -> " + to;
}
}
@Getter генерирует:
public ProductStatus getFrom() { return from; }
public ProductStatus getTo() { return to; }
from, to — payload exception-а, нужны handler-у в GlobalExceptionHandler для построения ProblemDetails. Без @Getter пришлось бы писать accessor-ы руками.
Lombok НЕ на records
JS-6.4: record уже всё генерирует.
// ✓ — обычный record
public record OrderCreatedEvent(
UUID eventId,
Long orderId,
Money amount,
Instant occurredAt
) {}
// ✗ — Lombok поверх record (компилятор ругается)
@Value
public record OrderCreatedEvent(UUID eventId, Long orderId) {}
@AllArgsConstructor
public record OrderCreatedEvent(UUID eventId, Long orderId) {}
Record даёт автоматически:
- Constructor (canonical).
- Accessors
eventId(),orderId()(безget-префикса). equals(),hashCode(),toString().
Lombok-аннотации избыточны. Если нужно custom-behavior — compact ctor:
public record OrderCreatedEvent(UUID eventId, Long orderId) {
public OrderCreatedEvent {
Objects.requireNonNull(eventId, "eventId");
Objects.requireNonNull(orderId, "orderId");
}
}
@Data ЗАПРЕЩЁН
JS-6.5: critical.
// КАТАСТРОФА
@Data
public class OrderEntity {
private Long id;
private OrderStatus status;
private List<OrderItem> items;
}
@Data генерирует:
@Getter+@Setterна все поля → mutable, ломает immutability.@EqualsAndHashCodeна все поля → ad-hoc equality (любое изменение поля = другой object в Set/Map).@ToString→ может logging PII (см. Auth → PII).@RequiredArgsConstructor— generates ctor только на final fields.
Что ломается:
- JPA entity с
@Data—equals()по всем полям, включая lazy-loaded collections → загружает их при сравнении. Performance hit. Set<OrderEntity>— два orders считаются разными если изменёнstatus.- PII утечка —
@ToStringвключает все поля по умолчанию.
Корректно:
// Immutable value object
public record Order(Long id, OrderStatus status, List<OrderItem> items) {}
// JPA entity — явные @Getter/@Setter
@Entity
@Getter
@Setter
@NoArgsConstructor(access = AccessLevel.PROTECTED)
public class OrderEntity {
@Id
private Long id;
private OrderStatus status;
}
Custom equals/hashCode если нужны — пишем руками по id.
@Builder точечно
JS-6.7: не везде.
// ✓ — сложный outgoing API request
@Builder
public record CreatePaymentRequest(
String idempotencyKey,
UUID orderId,
Money amount,
String currency,
PaymentMethod method,
Map<String, String> metadata,
@Nullable String description,
@Nullable Instant expirationDate
) {}
// usage
var request = CreatePaymentRequest.builder()
.idempotencyKey(idempotencyKey)
.orderId(order.id())
.amount(order.totalAmount())
.currency("RUB")
.method(PaymentMethod.CARD)
.build();
Когда @Builder уместен:
- 5+ полей в DTO.
- Опциональные поля (
@Nullable). - Сложная подготовка (через method chains).
Когда запрещён:
- На entity / aggregate root — построение агрегата через named-конструкторы (
Order.draft(...),Order.fromPersistence(...)); Lombok-builder обходит инварианты. - На простых POJOs с 2-3 полями — overkill.
@Getter/@Setter для mutable
JS-6.8: где record неприменим.
Применяется к:
- jOOQ POJOs (auto-generated, mutable обязательны).
- JPA entity (нужен no-arg ctor и setters).
- Mutable DTOs для draft-объектов, форм.
// jOOQ generated — mutable
@Getter
@Setter
public class OrdersPojo {
private Long id;
private OrderStatus status;
}
// JPA entity
@Entity
@Getter
@Setter
@NoArgsConstructor(access = AccessLevel.PROTECTED)
public class OrderEntity {
@Id
private Long id;
private OrderStatus status;
}
// Point-by-point — setter только где можно менять
@Getter
public class DraftOrder {
private final Long id; // только getter (final)
@Setter
private DeliveryAddress address; // setter точечно
}
Иерархия выбора:
recordдля immutable value object без поведения.@Value(Lombok) — immutable с методами или сложным constructor.@Getterбез@Setter— final поля, наследование (record неприменим).@Getter+@Setter— mutable (entity, draft, jOOQ POJO).- Ручные accessors — только при наличии логики.
Build settings
JS-6.6: одинаково везде.
// build.gradle.kts
dependencies {
compileOnly("org.projectlombok:lombok:1.18.34")
annotationProcessor("org.projectlombok:lombok:1.18.34")
testCompileOnly("org.projectlombok:lombok:1.18.34")
testAnnotationProcessor("org.projectlombok:lombok:1.18.34")
}
compileOnly (не implementation) — Lombok работает на этапе компиляции, в runtime classpath его быть не должно.
Что запрещено
| Антипаттерн | Правило | Что взамен |
|---|---|---|
| Явный all-args ctor для DI | JS-6.X1 | @RequiredArgsConstructor |
@AllArgsConstructor на DI-классе | JS-6.X2 | @RequiredArgsConstructor (только final) |
LoggerFactory.getLogger ручной | JS-6.2 | @Slf4j |
@Data в production | JS-6.5 | record или @Getter+@Setter явно |
Lombok поверх record | JS-6.4 | record даёт всё сам |
@Builder на aggregate root | JS-6.7 | named-конструкторы (Order.draft(...)) |
Lombok в implementation (не compileOnly) | JS-6.6 | compileOnly + annotationProcessor |
Ручные getXxx(), setXxx() для всех полей | JS-6.8 | @Getter/@Setter |
@RequiredArgsConstructor забыли на DI-классе | JS-6.1 | обязателен |
Куда дальше
- Java → раздел 6. Lombok — нормативные формулировки.
- Именование —
@Getterгенерируетget*префикс. - Современные фичи Java — record вместо
@Value/@Data. - Observability → logging —
@Slf4jдетали. - Auth → PII —
@ToStringриск утечки. - JOOQ → repository — generated POJOs с
@Getter/@Setter. - DDD → aggregate — named-конструкторы вместо
@Builder.