skip to content

What is the Command design pattern, and how do Java's Runnable and Callable embody it?

level: juniorimportance: must knowfreq 70%

answer

  1. Encapsulate a request as an object
  2. Runnable = run(), no result; Callable = call(), returns V + throws
  3. Executor is the invoker that consumes commands
  4. Decouples caller from when/where work runs

basics

~20 s

The Command pattern wraps a request as an object so you can pass it around, store it, and run it later. In Java, Runnable and Callable are command objects: each bundles a piece of work in one method (run or call) that something else executes when it chooses.

solid answer

~40 s

The Command pattern turns a request or action into a standalone object, decoupling the code that asks for work from the code that performs it. The object carries everything needed to do the job, so it can be passed, queued, logged, or deferred. Java realizes this with Runnable (a no-argument, no-result task whose work lives in run()) and Callable<V> (a task that returns a value of type V and may throw a checked exception, in call()). You hand these command objects to an executor, which decides when and on which thread to invoke them. This separation is exactly the Command pattern's intent: the caller does not know how or when the action runs, only that it is encapsulated and submittable.

go deeper

for a junior

Can state that Command wraps a request as an object and that Runnable/Callable are those objects handed to something that runs them later.

for a middle

Explains the Runnable-vs-Callable difference (result + checked exception) and that the ExecutorService is the invoker that decouples submission from execution.

for a senior

Maps the four classic roles (command/invoker/receiver/client) onto Java types and explains why object-ification unlocks queueing, scheduling, pooling, and result retrieval via Future.

for a principal

Frames Command as the conceptual backbone of the java.util.concurrent design — tasks as values enable backpressure, scheduling policies, and composition — and can discuss trade-offs versus direct invocation in API design.

## The problem the Command pattern solves Normally, when you want some code to run, you call a method directly: `service.doWork()`. The caller and the work are tightly bound together in time and place — the work runs *now*, on *this* thread, and the caller must know the exact method to call. Sometimes that is too rigid. You may want to: run the work *later*, run it on a *different thread*, put many pieces of work in a *queue*, *undo* it, *log* it, or *retry* it. To do any of that, the request itself must become a thing you can hold in a variable — an **object**. That is the **Command pattern**: *encapsulate a request as an object*, so the request can be stored, passed around, and executed by code that knows nothing about what the request actually does. ### The roles in the classic pattern - **Command**: an interface with a single method (often `execute()`). Concrete commands implement it. - **Invoker**: holds and triggers commands but does not know their contents (e.g. a button, a scheduler, a queue). - **Receiver**: the object the command actually operates on. - **Client**: creates the command and configures it. ## How Java embodies it: Runnable and Callable Java's standard library ships two ready-made Command interfaces: - **`Runnable`** — a *functional interface* (one abstract method) with the method `void run()`. It takes no arguments, returns nothing, and cannot throw checked exceptions. It is the simplest command: "do this work." - **`Callable<V>`** — a functional interface with the method `V call() throws Exception`. Unlike Runnable, it **returns a result** of type `V` and **may throw a checked exception**. It is the command for work that produces a value. Both are **command objects**: each instance bundles one unit of work. Because they are functional interfaces, you can create them with a lambda or method reference: ```java Runnable r = () -> System.out.println("hello"); Callable<Integer> c = () -> 2 + 2; ``` ## The invoker: the executor framework In modern Java, the **invoker** is usually an `ExecutorService` (an *executor*). You **submit** a command to it and the executor decides *when* and *on which thread* to run it. It may run it immediately, queue it, schedule it for later, or hand it to a pool of worker threads: ```java ExecutorService pool = Executors.newFixedThreadPool(4); pool.execute(r); // runs a Runnable, returns nothing Future<Integer> f = pool.submit(c); // runs a Callable, returns a Future for the result ``` This is the Command pattern in full: the executor (invoker) consumes encapsulated requests (Runnable/Callable) without knowing what they do. The caller is decoupled from *how, when, and where* the work runs. ## Why this matters Because the request is an object, the framework can do things impossible with a direct method call: pool threads, queue overflow work, schedule with a delay, retry, or collect results via `Future`. The encapsulation is what unlocks all of that. ## Key terms defined - **Functional interface**: an interface with exactly one abstract method, so it can be implemented by a lambda. - **Executor / ExecutorService**: an object that runs submitted tasks, abstracting away thread management. - **Future<V>**: a handle representing the eventual result of a Callable; you call `get()` to retrieve it (blocking until ready). - **Checked exception**: an exception the compiler forces you to declare or handle; Callable can throw these, Runnable cannot.

  • Why can a lambda be used wherever a Runnable or Callable is expected?
    Both are functional interfaces (a single abstract method), so a lambda or method reference whose shape matches that method is a valid implementation. The compiler infers the target type from context.
  • Name another classic Command pattern capability that Java's executor framework provides.
    Queueing: submitted tasks can wait in the executor's work queue until a thread is free, and ScheduledExecutorService can defer or repeat them — both rely on the request being a stored object.

saying these in an interview costs you the question

  • Saying Runnable returns a value (it does not; only Callable does)
  • Claiming the Command pattern is about threads — threading is just one use; the pattern is request-as-object
  • Thinking calling run() directly is the 'pattern' — without an invoker/executor there is no decoupling

context