Command Query Separation (CQS)
Definition

Violations
// VIOLATION 1: Query that modifies state
public class Stack<E> {
// BAD: pop() is both query AND command
public E pop() {
E element = elements[--size]; // Modifies state
return element; // Returns value
}
}
// VIOLATION 2: Save that returns saved entity
public class UserRepository {
// BAD: Command that returns data
public User save(User user) {
entityManager.persist(user);
return user; // Returns the saved user
}
}
// VIOLATION 3: Method that does too much
public class OrderService {
// BAD: Processes AND returns result
public Receipt processPayment(Order order) {
validateOrder(order);
chargeCard(order);
updateInventory(order);
sendConfirmation(order);
return generateReceipt(order); // Mixed!
}
}
Proper CQS
// GOOD: Separate Commands and Queries
public class Stack<E> {
// QUERY: Returns top element without removing
public E peek() {
if (isEmpty()) throw new EmptyStackException();
return elements[size - 1];
}
// COMMAND: Removes top element
public void pop() {
if (isEmpty()) throw new EmptyStackException();
elements[--size] = null;
}
// Usage (two calls instead of one)
E top = stack.peek();
stack.pop();
}
public class UserRepository {
// COMMAND: Saves user
public void save(User user) {
entityManager.persist(user);
}
// QUERY: Finds user
public Optional<User> findById(String id) {
return Optional.ofNullable(entityManager.find(User.class, id));
}
// Usage
repository.save(user);
User saved = repository.findById(user.getId()).orElseThrow();
}
public class OrderService {
// COMMAND: Processes order
public void processOrder(Order order) {
validateOrder(order);
chargeCard(order);
updateInventory(order);
sendConfirmation(order);
}
// QUERY: Gets receipt
public Receipt getReceipt(String orderId) {
Order order = orderRepository.findById(orderId).orElseThrow();
return receiptGenerator.generate(order);
}
}
Exceptions to CQS

CQRS (Command Query Responsibility Segregation)

CQS in Practice
// Real-world service design following CQS
public interface UserService {
// ========== COMMANDS (return void) ==========
void createUser(CreateUserCommand command);
void updateProfile(UpdateProfileCommand command);
void changePassword(ChangePasswordCommand command);
void deactivateUser(String userId);
// ========== QUERIES (return data) ==========
Optional<UserDTO> findById(String id);
List<UserDTO> findByRole(Role role);
Page<UserDTO> search(UserSearchCriteria criteria, Pageable page);
boolean existsByEmail(String email);
UserStats getStatistics(String userId);
}
// Command objects (immutable)
public record CreateUserCommand(
String email,
String name,
String password
) {}
public record UpdateProfileCommand(
String userId,
String name,
String bio,
String avatarUrl
) {}
// Implementation
@Service
public class UserServiceImpl implements UserService {
@Override
@Transactional
public void createUser(CreateUserCommand command) {
// Validate
if (userRepository.existsByEmail(command.email())) {
throw new EmailAlreadyExistsException(command.email());
}
// Execute command
User user = new User(command.email(), command.name());
user.setPassword(passwordEncoder.encode(command.password()));
userRepository.save(user);
// Side effects
eventPublisher.publish(new UserCreatedEvent(user.getId()));
// No return value - pure command
}
@Override
@Transactional(readOnly = true) // Optimization for queries
public Optional<UserDTO> findById(String id) {
return userRepository.findById(id)
.map(this::toDTO);
// No side effects - pure query
}
}
Tips & Tricks
