Loading learning content...
Imagine a class with five instance variables. Now ask: does every method use most of these variables? Or are there methods that only touch one variable while ignoring the others? Are there variables that only one method ever accesses?
If you can draw a line through the middle of your class, with some methods using some variables on one side and other methods using different variables on the other, you've discovered the hidden seam of low cohesion. Your class is really two classes masquerading as one.
Cohesion is the degree to which the elements of a class belong together. High cohesion means every field and every method works together toward the same purpose. Low cohesion means unrelated functionality has been bundled into a single class by accident of history.
By the end of this page, you will understand how to measure and evaluate class cohesion, recognize the patterns that indicate low cohesion, and know how to design and refactor classes for maximum cohesion.
Cohesion measures how strongly related and focused the responsibilities of a class are. A highly cohesive class does one thing, and every part of the class contributes to doing that thing well.
Formally, cohesion can be measured by examining how instance variables and methods relate to each other:
123456789101112131415161718192021222324252627282930313233343536373839404142434445
/** * A highly cohesive class: all members work together. * * Instance Variables: balance, transactions, accountNumber * Every method uses or modifies the shared state. */public class BankAccount { private final String accountNumber; // Used by: getStatement, equals private Money balance; // Used by: deposit, withdraw, getBalance, getStatement private List<Transaction> transactions; // Used by: deposit, withdraw, getStatement public BankAccount(String accountNumber) { this.accountNumber = accountNumber; this.balance = Money.ZERO; this.transactions = new ArrayList<>(); } // Uses: balance, transactions public void deposit(Money amount) { this.balance = this.balance.add(amount); this.transactions.add(Transaction.credit(amount)); } // Uses: balance, transactions public void withdraw(Money amount) { if (amount.isGreaterThan(this.balance)) { throw new InsufficientFundsException(this.balance, amount); } this.balance = this.balance.subtract(amount); this.transactions.add(Transaction.debit(amount)); } // Uses: balance public Money getBalance() { return this.balance; } // Uses: accountNumber, balance, transactions public Statement getStatement(DateRange period) { List<Transaction> relevant = this.transactions.stream() .filter(t -> period.contains(t.date())) .toList(); return new Statement(this.accountNumber, this.balance, relevant); }}Draw a matrix: rows are methods, columns are instance variables. Put a dot where a method uses a variable. High cohesion fills most of the matrix. Low cohesion shows isolated clusters that don't overlap—those clusters should probably be separate classes.
Software engineering literature identifies several levels of cohesion, from weakest to strongest. Understanding this spectrum helps you recognize and improve the cohesion of your classes.
| Type | Description | Example | Quality |
|---|---|---|---|
| Coincidental | Elements have no meaningful relationship; bundled arbitrarily | Utilities class with random helper methods | ❌ Worst |
| Logical | Elements do similar things but on different data, selected by flag | PrintReport(type) with if/else for each type | ❌ Poor |
| Temporal | Elements execute at the same time but are otherwise unrelated | Startup class that initializes logging, database, and cache | ⚠️ Weak |
| Procedural | Elements are part of the same procedure, executed in sequence | ParseAndValidateAndSave method | ⚠️ Weak |
| Communicational | Elements operate on the same data | CustomerDataProcessor reading/writing customer | ✓ Moderate |
| Sequential | Output of one element feeds into another | Pipeline: Read → Transform → Write | ✓ Good |
| Functional | All elements contribute to a single, well-defined task | Stack class: push, pop, peek, isEmpty | ✓ Best |
Stack pushes, pops, and reports its state—all aspects of the same abstraction.Low cohesion doesn't announce itself with syntax errors. It lurks in seemingly working code, gradually making the class harder to understand and maintain. Here are the telltale signs:
process(data, isExport). This often indicates merged responsibilities.12345678910111213141516171819202122232425262728293031323334353637
// ❌ LOW COHESION: Two distinct responsibilities in one class public class ReportGenerator { // === CLIQUE 1: Data Retrieval === private final Database database; // Used ONLY by Clique 1 private final QueryBuilder queryBuilder; // Used ONLY by Clique 1 // === CLIQUE 2: PDF Generation === private final PdfLibrary pdfLibrary; // Used ONLY by Clique 2 private final FontManager fontManager; // Used ONLY by Clique 2 private final TemplateEngine templates; // Used ONLY by Clique 2 // === Clique 1 Methods === public ReportData fetchSalesData(DateRange range) { // Uses: database, queryBuilder // Does NOT use: pdfLibrary, fontManager, templates } public ReportData fetchInventoryData(Warehouse warehouse) { // Uses: database, queryBuilder // Does NOT use: pdfLibrary, fontManager, templates } // === Clique 2 Methods === public byte[] generatePdf(ReportData data) { // Uses: pdfLibrary, fontManager, templates // Does NOT use: database, queryBuilder } public byte[] generateInvoicePdf(Invoice invoice) { // Uses: pdfLibrary, fontManager, templates // Does NOT use: database, queryBuilder } // THE FIX: Split into ReportDataFetcher and PdfGenerator}If you can partition a class's instance variables into groups where each method only uses one group, you've found the natural class boundaries. Each variable group should become its own class.
While intuition guides much design work, formal metrics can objectively identify cohesion problems. The most common is LCOM (Lack of Cohesion of Methods), which quantifies how methods share instance variables.
123456789101112131415161718192021222324252627282930313233343536373839404142
/** * LCOM Analysis Example * * Class with 3 methods (m1, m2, m3) and 4 variables (a, b, c, d) * * Method-Variable Matrix: * a b c d * m1 ✓ ✓ * m2 ✓ ✓ * m3 ✓ ✓ * * Graph visualization: * [m1]---a---[m2] [m3]---c---[d] * \ / \ / * -b- (isolated!) * * LCOM3 = 2 (two connected components) * This class should be split into TWO classes! */ // Original: Low cohesionclass Original { int a, b, c, d; void m1() { /* uses a, b */ } void m2() { /* uses a, b */ } void m3() { /* uses c, d */ } // Disconnected!} // After split: Two highly cohesive classesclass ComponentOne { int a, b; void m1() { /* uses a, b */ } void m2() { /* uses a, b */ } // LCOM3 = 1 ✓} class ComponentTwo { int c, d; void m3() { /* uses c, d */ } // LCOM3 = 1 ✓}Most static analysis tools (SonarQube, CodeClimate, IntelliJ's built-in analysis) can compute LCOM metrics. Use them as early warning systems: when LCOM scores spike, investigate the class's cohesion.
Rather than detecting and fixing low cohesion after the fact, design classes with cohesion in mind from the start. These strategies help you create cohesive classes naturally:
Customer class shouldn't have sendEmail()—that's EmailService's job, even if customers receive emails.A useful heuristic: every method should use at least half of the instance variables. If a method only touches one field, question why it's a method of this class rather than a free function or a method of another class.
When you've identified low cohesion in existing code, specific refactoring techniques can improve it. Each technique addresses a particular cohesion problem:
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960
// BEFORE: Low cohesion - Employee with embedded phone handling class Employee { private String name; private String department; private double salary; // Phone fields - separate concern! private String areaCode; private String number; private String extension; public String getPhoneNumber() { return "(" + areaCode + ") " + number + " ext " + extension; } public void updatePhone(String area, String num, String ext) { this.areaCode = area; this.number = num; this.extension = ext; } public boolean isSameAreaCode(String area) { return this.areaCode.equals(area); } // Employee methods that don't touch phone...} // AFTER: Extracted TelephoneNumber class class TelephoneNumber { private final String areaCode; private final String number; private final String extension; public TelephoneNumber(String areaCode, String number, String extension) { this.areaCode = areaCode; this.number = number; this.extension = extension; } public String formatted() { return "(" + areaCode + ") " + number + " ext " + extension; } public boolean isInArea(String area) { return this.areaCode.equals(area); }} class Employee { private String name; private String department; private double salary; private TelephoneNumber phone; // Cohesive composition // Employee now focused on employee concerns}Cohesion and coupling are two sides of the same design coin. They work together to determine how maintainable a system is:
The goal is high cohesion, low coupling. Classes should be internally unified but externally independent.
Increasing cohesion sometimes increases coupling (moving methods requires new dependencies). Decreasing coupling sometimes decreases cohesion (introducing intermediaries). The art is finding the balance where both are acceptable. Usually, prioritize cohesion within classes and manage coupling through well-designed interfaces.
Cohesion is the invisible force that makes classes understandable and maintainable. When every member of a class contributes to the same purpose, the class becomes a coherent unit—easy to understand, easy to test, and easy to change.
What's next:
With single purpose, meaningful names, appropriate size, and cohesive members established, we conclude with a class design checklist—a practical summary of all principles that you can apply to every class you design or review.
You now understand class cohesion—what it means, how to measure it, how to design for it, and how to refactor toward it. Next, we'll consolidate everything into an actionable class design checklist.