Skills for creating, testing, reviewing, migrating and guarding Spring Boot 4 projects with Java 25: project scaffolding with architecture patterns, Boot 4 test templates, code review, Boot 3 to 4 migration, and a team harness of build gates and agent hooks.
93%
Does it follow best practices?
Run evals on this skill
Adds up to 20 points to the overall score
Low
Low-risk findings worth noting
Use this file to judge whether the domain model fits the domain. Full rich-entity template: ../../creating-springboot-projects/assets/rich-entity.java. Value objects: value-objects-patterns.md.
| Aspect | Anemic + primitives | Rich + value objects |
|---|---|---|
| Entity | Fields, getters, setters; no rules | Private state, behavior methods, factory methods |
| Business rules | In services (transaction scripts) | In entities and VOs; services orchestrate |
| Validation | Repeated in services | Once, at creation |
| Types | String, Long, BigDecimal | OrderNumber, OrderId, Money |
| Tests | Through services | Unit tests on the domain |
| Cost | Low at first, grows with rules | Higher at first |
| Fits | Simple CRUD, prototypes, admin tools | Complex rules, state transitions, money, long-lived systems |
An anemic model is acceptable for simple CRUD. Do not flag it there. Flag it when services contain state-transition rules, repeated validation, or bugs from mixed-up primitives.
Primitive obsession. Several parameters of the same primitive type that can be swapped without a compile error:
void processOrder(String orderNumber, String email, String productCode) // swappable
void processOrder(OrderNumber orderNumber, EmailAddress email, ProductCode code) // compiler catches swapsRules in services that belong to the entity. Status checks followed by setters:
// Flag: the service enforces the rule; any other caller can skip it
if (order.getStatus() != OrderStatus.PENDING) throw new IllegalStateException();
order.setStatus(OrderStatus.CANCELLED);
// Fix: the entity enforces it
order.cancel(); // throws if the status does not allow cancellingPublic setters on a rich entity. Replace them with intent methods (cancel(), confirm(), adjustPrice(Money)).
Constructors with many parameters. Use named factory methods (Order.createNew(quantity, unitPrice)) that build a valid object.
Invalid states. Fields that can contradict each other (endDate before startDate, total that does not match the items). Enforce the invariant in the entity or a VO.
protected, not public.@Id is on a field.@Embeddable (records allowed on Hibernate 6.2+).@Transactional
public void cancelOrder(OrderId id) {
Order order = repository.findById(id).orElseThrow();
order.cancel();
eventPublisher.publishEvent(new OrderCancelled(id));
}Inside a transaction, a managed entity is saved at commit. An explicit save() is not needed.
Do it step by step, starting with the riskiest concepts:
Do not convert everything at once, and do not apply rich models to trivial entities.