Command-Query Separation (CQS) with Java Examples

Introduction

This tutorial explains Command-Query Separation (CQS) with Java examples. We will start with what commands and queries are and what the principle asks of them. We will then run an example in which a harmless-looking getter also resets a counter, so that showing overtime on a dashboard means it is never paid - and fix it by separating the query from the command. Next, we will look at the well-known exceptions in the JDK, such as pop() and Iterator.next(), and why they exist. Lastly, we will see how CQS differs from CQRS.

What is Command-Query Separation

Bertrand Meyer introduced Command-Query Separation in his book Object-Oriented Software Construction (1988), and built it into the Eiffel language. It divides methods into two kinds -
  • Queries return a result and do not change the observable state of the system. list.size() and employee.leaveBalance() are queries.
  • Commands change the state of the system and do not return a value. list.clear() and employee.takeLeave(3) are commands.
The principle says that every method should be one or the other, never both. It is often summed up as "asking a question should not change the answer".
Command Query Separation - methods that change state are commands returning void, methods that return values are queries with no side effects

Why it matters

If queries never change anything, you can call them as often as you like, in any order, and from anywhere - in a log statement, in an assertion, in a debugger's watch window, in a test - without changing what the program does. You can also tell what a line of code does from its shape: x = obj.something() only reads, and obj.doSomething() changes things. A method that does both breaks these assumptions in a way that does not show at the call site.

Example - a getter that changes state

An employee's overtime hours are logged during the month. In the first version, getOvertimeHours() returns the hours and resets the counter, because the original author only expected payroll to call it, once a month. Later, a dashboard was added that shows the current overtime.
Java code showing a query with a side effect and the same class following Command-Query Separation
//CqsExample.java
public class CqsExample {

  // ---------- Breaks CQS: a "getter" that also resets ----------
  static class TimesheetMixed {
    private int overtimeHours;

    void logOvertime(int hours) { overtimeHours += hours; }

    int getOvertimeHours() {            // looks like a query...
      int hours = overtimeHours;
      overtimeHours = 0;                // ...but also resets the counter
      return hours;
    }
  }

  // ---------- Follows CQS ----------
  static class Timesheet {
    private int overtimeHours;

    void logOvertime(int hours) { overtimeHours += hours; }   // command
    int overtimeHours() { return overtimeHours; }             // query
    void settleOvertime() { overtimeHours = 0; }              // command
  }

  static double overtimePay(int hours) { return hours * 30.0; }

  public static void main(String[] args) {
    TimesheetMixed mixed = new TimesheetMixed();
    mixed.logOvertime(4);
    mixed.logOvertime(2);
    System.out.println("Mixed - dashboard shows : " + mixed.getOvertimeHours() + " hours");
    System.out.println("Mixed - payroll pays    : " + overtimePay(mixed.getOvertimeHours()));

    Timesheet timesheet = new Timesheet();
    timesheet.logOvertime(4);
    timesheet.logOvertime(2);
    System.out.println("CQS   - dashboard shows : " + timesheet.overtimeHours() + " hours");
    int hours = timesheet.overtimeHours();
    System.out.println("CQS   - payroll pays    : " + overtimePay(hours));
    timesheet.settleOvertime();
    System.out.println("CQS   - after settling  : " + timesheet.overtimeHours() + " hours");
  }
}
 OUTPUT of the above code
Mixed - dashboard shows : 6 hours
Mixed - payroll pays    : 0.0
CQS   - dashboard shows : 6 hours
CQS   - payroll pays    : 180.0
CQS   - after settling  : 0 hours

Explanation of the code

  • In TimesheetMixed, the dashboard's call to getOvertimeHours() reset the counter, so payroll found 0 hours and paid nothing. Neither caller did anything wrong; the method's name promised a query and it behaved as a command.
  • The same bug appears if anyone adds log.debug("overtime = " + ts.getOvertimeHours()), or inspects the method in a debugger. Code that only looks at a value has changed it.
  • Timesheet splits the work into a query, overtimeHours(), and a command, settleOvertime(). The dashboard can call the query as often as it likes; payroll reads the hours and then settles them, as two visible steps.
  • The command's name says what it does. A reader of the payroll code now sees that overtime is reset, instead of having to know that a getter does it.

Accepted exceptions in the JDK

CQS is a strong default, not an absolute rule. Several JDK methods both change state and return a value, deliberately -
JDK methods that are both a command and a query
String top = stack.pop();                 // removes the top element and returns it
Employee next = iterator.next();          // advances the iterator and returns an element
Employee old = map.put(id, employee);     // replaces a value and returns the previous one
Task task = queue.poll();                 // removes the head of the queue and returns it
int n = counter.incrementAndGet();        // AtomicInteger: increments and returns the new value
There are two good reasons for these -
  • Atomicity in concurrent code: with several threads, "read the value, then change it" as two calls is a race condition, because another thread can act in between. incrementAndGet() and ConcurrentHashMap.putIfAbsent() must read and write in one step to be correct.
  • Established idioms: pop() and next() are so well known that no reader is surprised by them. The names announce the state change.
When you break CQS, do it for one of these reasons, and give the method a name that makes the change obvious - takeNext() or readAndReset(), never get...().

Commands that return something

Two other common cases sit at the edge of CQS -
  • Fluent setters and builders return this so that calls can be chained. They return no information about the object's state, so they keep the spirit of CQS.
  • Creating something and returning its id, such as repository.save(employee) returning a generated key. This is widely accepted, because the caller needs the result of the command and there is no separate question it could ask. Alternatively, let the caller generate the id first, and make save() a pure command.
Returning a boolean success flag from a command is usually better replaced by an exception for the failure case, as takeLeave() in the Tell, Don't Ask tutorialClick to read tutorial: Tell, Don't Ask Principle Explained with Java Examples does.

CQS versus CQRS

CQRS (Command Query Responsibility Segregation), named by Greg Young around 2010, takes the same idea to the architecture level. Instead of separating methods, it separates whole models: commands go to a write model that enforces business rules, and queries are answered from one or more read models shaped for the screens and reports that need them, often in different storage. CQRS is a large architectural decision for complex domains; CQS is a habit that costs nothing and fits in every class.

CQS and other principles

Summary

In this tutorial we first saw Bertrand Meyer's Command-Query Separation: queries return results without changing state, and commands change state without returning results. Next we saw why this makes queries safe to call from anywhere. We then ran an example in which a getter that reset a counter caused overtime to go unpaid as soon as a dashboard displayed it, and fixed it with a separate query and command. We then looked at the JDK's deliberate exceptions, such as pop() and incrementAndGet(), and the reasons for them, and at commands that return this or a generated id. Lastly, we compared CQS with CQRS and related it to other principles.