Appearance
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:
| Pattern | What It Really Is | Functional Equivalent |
|---|---|---|
| Strategy | A function passed as a parameter | A function parameter |
| Command | A deferred function call | A closure |
| Observer | A callback registration | Event handler / callback |
| Template Method | A function with a customizable step | Higher-order function |
| Visitor | Double dispatch to a function | Pattern matching |
| Factory | A function that returns objects | A 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:
- Nominal typing overhead — You must declare an interface before you can pass behavior around.
- Single-method classes — Classes like
Runnable,Callable,Comparator, andActionListenerexist solely to wrap a single function. - Indirection proliferation — Layers of abstraction accumulate not because of genuine complexity, but because the language demands it.
- 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
BankAccounthas a balance and operations on it. - Multiple implementations genuinely exist — A
PaymentProcessorwith Stripe, PayPal, and Square backends. - Domain modeling benefits from rich types — An
Order,LineItem, andShippingAddressrepresent real business entities. - You need to enforce invariants — A
Moneyclass that prevents mixing currencies.
The problem isn't objects — it's objects for the sake of objects.
Best Practices
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.
Count your types per feature: If adding a simple feature requires creating more than 2–3 new files, question whether you're over-abstracting.
Prefer functions for stateless behavior: Transformations, validations, and predicates are verbs — let them be functions, not single-method classes.
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.
Leverage modern Java features: Lambdas, method references, records, sealed interfaces, and pattern matching let you express intent with less ceremony.
Name classes after domain concepts, not mechanical roles:
Orderis good;OrderProcessingStrategyFactoryshould make you pause and reconsider.Recognize when design patterns are language workarounds: Strategy, Command, and Observer are often compensating for a lack of first-class functions — use lambdas instead.
Prefer composition over deep hierarchies: Functional composition (
andThen,compose) often replaces the need for Template Method and Decorator patterns.Read the original essay: Yegge's writing is entertaining and insightful — read it directly at steve-yegge.blogspot.com for the full satirical experience.
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.
Related Concepts
- 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 —
CompletableFutureand reactive patterns show how first-class functions simplify concurrency.