Skip to content

Steve Yegge's Kingdom of Nouns

Introduction

In 2006, Steve Yegge published a satirical essay titled "Execution in the Kingdom of Nouns" that became one of the most influential critiques of Java's object-oriented programming style. The essay argues that Java's insistence on wrapping every action inside a class (a noun) leads to bloated, over-engineered code — and that by banishing first-class functions (verbs), Java forces developers into contorted designs full of unnecessary abstractions. Understanding this critique is essential for any developer who wants to write clean, expressive code and recognize when design patterns become anti-patterns.

Core Concepts

The Central Metaphor

Yegge's essay imagines a kingdom where Nouns (objects, classes) are royalty and Verbs (functions, actions) are second-class citizens that cannot exist independently. Every verb must be escorted by a noun. You can never just "sort" — you must create a Sorter. You can never just "compare" — you must instantiate a Comparator. You can never simply "execute" — you must build an ExecutionStrategyFactory.

This mirrors Java's design constraint (prior to Java 8): functions cannot exist outside of a class. Every piece of behavior must be attached to an object, leading to an explosion of single-method classes, interfaces, and wrapper objects.

The Real-World Problem

Consider a simple task: taking a list of strings and filtering them by length. In a language with first-class functions, this is trivial:

javascript
const result = words.filter(w => w.length > 5);

In pre-Java-8 style, the same operation required ceremony:

java
List<String> result = new ArrayList<>();
for (String word : words) {
    if (word.length() > 5) {
        result.add(word);
    }
}

Or, if you wanted to make it "reusable" and "object-oriented":

java
public interface StringFilter {
    boolean accept(String s);
}

public class LengthGreaterThanFilter implements StringFilter {
    private final int minLength;

    public LengthGreaterThanFilter(int minLength) {
        this.minLength = minLength;
    }

    @Override
    public boolean accept(String s) {
        return s.length() > minLength;
    }
}

public class StringFilterExecutor {
    public List<String> filter(List<String> input, StringFilter filter) {
        List<String> result = new ArrayList<>();
        for (String s : input) {
            if (filter.accept(s)) {
                result.add(s);
            }
        }
        return result;
    }
}

You now have an interface, a concrete implementation class, and an executor class — three nouns — just to express a single verb: filter.

The Pattern Explosion

Yegge's critique targets the classic Gang of Four design patterns, many of which exist solely because the language lacks first-class functions:

PatternWhat It Really IsFunctional Equivalent
StrategyA function passed as a parameterA function parameter
CommandA deferred function callA closure
ObserverA callback registrationEvent handler / callback
Template MethodA function with a customizable stepHigher-order function
VisitorDouble dispatch to a functionPattern matching
FactoryA function that returns objectsA constructor function

Many of these patterns are valuable architectural concepts, but Yegge's point is that their implementation in Java requires far more boilerplate than necessary.

Why This Happens

The root cause is a language-level constraint: if functions are not first-class values, every behavior must be wrapped in an object. This leads to:

  1. Nominal typing overhead — You must declare an interface before you can pass behavior around.
  2. Single-method classes — Classes like Runnable, Callable, Comparator, and ActionListener exist solely to wrap a single function.
  3. Indirection proliferation — Layers of abstraction accumulate not because of genuine complexity, but because the language demands it.
  4. Naming contortions — You get class names like AbstractSingletonProxyFactoryBean (a real Spring class) because every concept must be a noun.

The Anatomy of Over-Abstraction

Let's examine a realistic example of the Kingdom of Nouns in action. Suppose we want to send a notification to a user.

The Over-Engineered Approach

java
// Step 1: Define the abstraction
public interface NotificationSender {
    void send(Notification notification, User recipient);
}

// Step 2: Define the notification
public class Notification {
    private final String message;
    private final NotificationType type;
    private final NotificationPriority priority;
    // constructor, getters...
}

// Step 3: Define supporting enums and types
public enum NotificationType { EMAIL, SMS, PUSH }
public enum NotificationPriority { LOW, MEDIUM, HIGH }

// Step 4: Implement the sender
public class EmailNotificationSender implements NotificationSender {
    private final EmailService emailService;
    private final NotificationFormatter formatter;
    private final NotificationValidator validator;

    public EmailNotificationSender(
            EmailService emailService,
            NotificationFormatter formatter,
            NotificationValidator validator) {
        this.emailService = emailService;
        this.formatter = formatter;
        this.validator = validator;
    }

    @Override
    public void send(Notification notification, User recipient) {
        validator.validate(notification);
        String formatted = formatter.format(notification);
        emailService.sendEmail(recipient.getEmail(), formatted);
    }
}

// Step 5: Create a factory
public class NotificationSenderFactory {
    public NotificationSender createSender(NotificationType type) {
        switch (type) {
            case EMAIL: return new EmailNotificationSender(/*...dependencies...*/);
            case SMS: return new SmsNotificationSender(/*...dependencies...*/);
            case PUSH: return new PushNotificationSender(/*...dependencies...*/);
            default: throw new UnsupportedNotificationTypeException(type);
        }
    }
}

// Step 6: Create a service that uses the factory
public class NotificationService {
    private final NotificationSenderFactory factory;

    public void notifyUser(User user, String message, NotificationType type) {
        Notification notification = new Notification(message, type, NotificationPriority.MEDIUM);
        NotificationSender sender = factory.createSender(type);
        sender.send(notification, user);
    }
}

We now have at least 9 classes and interfaces to send a message to a user. Each one is a noun.

The Pragmatic Approach

java
public class NotificationService {
    private final Map<String, Consumer<NotificationRequest>> channels;

    public NotificationService() {
        channels = Map.of(
            "email", req -> sendEmail(req),
            "sms",   req -> sendSms(req),
            "push",  req -> sendPush(req)
        );
    }

    public void notify(String channel, String recipient, String message) {
        channels.getOrDefault(channel, req -> {
            throw new IllegalArgumentException("Unknown channel: " + req.channel());
        }).accept(new NotificationRequest(channel, recipient, message));
    }

    private void sendEmail(NotificationRequest req) { /* ... */ }
    private void sendSms(NotificationRequest req)   { /* ... */ }
    private void sendPush(NotificationRequest req)  { /* ... */ }

    record NotificationRequest(String channel, String recipient, String message) {}
}

One class, a few methods, and a map of functions. The same extensibility, a fraction of the ceremony.

Java's Evolution: Reclaiming the Verbs

Java 8 (released in 2014) was a direct response to many of the problems Yegge identified. With lambdas, method references, and the java.util.function package, verbs finally gained some citizenship in the kingdom.

Before Java 8 (Kingdom of Nouns)

java
// Sorting with a custom comparator
Collections.sort(people, new Comparator<Person>() {
    @Override
    public int compare(Person a, Person b) {
        return a.getName().compareTo(b.getName());
    }
});

// Running a thread
new Thread(new Runnable() {
    @Override
    public void run() {
        System.out.println("Hello from thread");
    }
}).start();

After Java 8 (Verbs Allowed)

java
// Sorting
people.sort(Comparator.comparing(Person::getName));

// Running a thread
new Thread(() -> System.out.println("Hello from thread")).start();

However, Yegge's critique goes beyond language features. It targets a cultural tendency in the Java ecosystem to reach for abstraction, indirection, and design patterns when simpler approaches would suffice.

The Deeper Lesson: When Patterns Become Cargo Cult

The essay's most enduring insight isn't about Java specifically — it's about confusing the map for the territory. Design patterns are tools for solving specific problems, not religious rituals to perform on every codebase.

Signs You're in the Kingdom of Nouns

A Complete Example: Escaping the Kingdom

Let's build a small data processing pipeline both ways.

Kingdom of Nouns Version

java
import java.util.ArrayList;
import java.util.List;

// Step 1: Define transformer interface
interface DataTransformer<T, R> {
    R transform(T input);
}

// Step 2: Define filter interface
interface DataFilter<T> {
    boolean test(T input);
}

// Step 3: Define aggregator interface
interface DataAggregator<T, R> {
    R aggregate(List<T> input);
}

// Step 4: Implement concrete transformer
class UpperCaseTransformer implements DataTransformer<String, String> {
    @Override
    public String transform(String input) {
        return input.toUpperCase();
    }
}

// Step 5: Implement concrete filter
class NonEmptyFilter implements DataFilter<String> {
    @Override
    public boolean test(String input) {
        return input != null && !input.isEmpty();
    }
}

// Step 6: Implement concrete aggregator
class StringJoiningAggregator implements DataAggregator<String, String> {
    private final String delimiter;

    StringJoiningAggregator(String delimiter) {
        this.delimiter = delimiter;
    }

    @Override
    public String aggregate(List<String> input) {
        return String.join(delimiter, input);
    }
}

// Step 7: Build pipeline executor
class DataPipelineExecutor<T, R> {
    private final DataFilter<T> filter;
    private final DataTransformer<T, T> transformer;
    private final DataAggregator<T, R> aggregator;

    DataPipelineExecutor(
            DataFilter<T> filter,
            DataTransformer<T, T> transformer,
            DataAggregator<T, R> aggregator) {
        this.filter = filter;
        this.transformer = transformer;
        this.aggregator = aggregator;
    }

    public R execute(List<T> data) {
        List<T> result = new ArrayList<>();
        for (T item : data) {
            if (filter.test(item)) {
                result.add(transformer.transform(item));
            }
        }
        return aggregator.aggregate(result);
    }
}

// Step 8: Main
public class KingdomOfNounsDemo {
    public static void main(String[] args) {
        List<String> data = List.of("hello", "", "world", "foo", "", "bar");

        DataPipelineExecutor<String, String> executor =
                new DataPipelineExecutor<>(
                        new NonEmptyFilter(),
                        new UpperCaseTransformer(),
                        new StringJoiningAggregator(", ")
                );

        String result = executor.execute(data);
        System.out.println(result); // HELLO, WORLD, FOO, BAR
    }
}

7 types (3 interfaces, 3 implementations, 1 executor) for filtering, transforming, and joining strings.

Liberated Version (Modern Java)

java
import java.util.List;
import java.util.stream.Collectors;

public class LiberatedDemo {
    public static void main(String[] args) {
        List<String> data = List.of("hello", "", "world", "foo", "", "bar");

        String result = data.stream()
                .filter(s -> !s.isEmpty())
                .map(String::toUpperCase)
                .collect(Collectors.joining(", "));

        System.out.println(result); // HELLO, WORLD, FOO, BAR
    }
}

1 class, 5 lines of logic. The verbs — filter, map, collect — stand on their own.

When Nouns ARE Appropriate

Yegge's critique, taken to the extreme, would lead to the opposite problem: massive procedural codebases with no structure. The key is calibrating abstraction to actual complexity.

Nouns are the right tool when:

  • State and behavior are genuinely coupled — A BankAccount has a balance and operations on it.
  • Multiple implementations genuinely exist — A PaymentProcessor with Stripe, PayPal, and Square backends.
  • Domain modeling benefits from rich types — An Order, LineItem, and ShippingAddress represent real business entities.
  • You need to enforce invariants — A Money class that prevents mixing currencies.

The problem isn't objects — it's objects for the sake of objects.

Best Practices

  1. Ask "what does this do?" before "what is this?": If the answer to the first question is a single verb, you probably don't need a new class — a function will suffice.

  2. Count your types per feature: If adding a simple feature requires creating more than 2–3 new files, question whether you're over-abstracting.

  3. Prefer functions for stateless behavior: Transformations, validations, and predicates are verbs — let them be functions, not single-method classes.

  4. Use the Rule of Three: Don't abstract until you've seen the same pattern three times. Premature abstraction is as harmful as premature optimization.

  5. Leverage modern Java features: Lambdas, method references, records, sealed interfaces, and pattern matching let you express intent with less ceremony.

  6. Name classes after domain concepts, not mechanical roles: Order is good; OrderProcessingStrategyFactory should make you pause and reconsider.

  7. Recognize when design patterns are language workarounds: Strategy, Command, and Observer are often compensating for a lack of first-class functions — use lambdas instead.

  8. Prefer composition over deep hierarchies: Functional composition (andThen, compose) often replaces the need for Template Method and Decorator patterns.

  9. Read the original essay: Yegge's writing is entertaining and insightful — read it directly at steve-yegge.blogspot.com for the full satirical experience.

  10. Balance is the goal: Neither a pure-noun kingdom nor a verb-only anarchy produces good software. Aim for the right abstraction at the right level.

  • Single Responsibility Principle: SRP is often invoked to justify class explosion, but Yegge's essay reminds us that a single responsibility can be expressed as a function, not just a class.
  • Open/Closed Principle: The OCP drives much of the interface-and-implementation pattern. Modern approaches using higher-order functions achieve the same extensibility with less ceremony.
  • Interface Segregation Principle: ISP actually aligns with Yegge's critique — it argues against bloated interfaces, which arise naturally in noun-heavy designs.
  • Dependency Inversion Principle: DIP is a core driver of the factory-and-interface patterns Yegge satirizes, but the principle itself is sound — only the implementation style is questioned.
  • Asynchronous Programming: Callback-heavy async code is a prime example of verb-wrapping — CompletableFuture and reactive patterns show how first-class functions simplify concurrency.