Loading learning content...
A house has rooms. But when a house is demolished, what happens to its rooms? They don't get "moved" to another house or "released" into the wild. The rooms simply cease to exist. A room cannot exist without the house that contains it—its identity, purpose, and very existence are inseparable from the whole.
This is composition—the strongest form of whole-part relationship in object-oriented design. Where aggregation says "I have these, and they can survive without me," composition says "I own these completely, and when I go, they go with me."
Understanding composition is critical for correctly modeling lifecycles, managing memory and resources, and designing systems that behave predictably when objects are created and destroyed.
By the end of this page, you will understand: (1) The precise definition of composition and exclusive ownership, (2) How composition differs from aggregation and association, (3) Lifecycle management in composition, (4) How to represent composition in UML diagrams, (5) Implementation patterns for composition in code, (6) The relationship between composition and immutability, and (7) When to choose composition vs aggregation.
Composition is a specialized form of aggregation that represents a "whole-part" relationship with exclusive ownership and lifecycle dependency. The whole is responsible for creating, managing, and destroying its parts.
In composition:
In UML, composition (also called composite aggregation) is a form of aggregation that requires a part object be included in at most one composite object at a time. If the composite object is deleted, all of its part objects are deleted with it. It is shown with a filled (solid) diamond at the whole end of the relationship.
Key Characteristics of Composition:
Exclusive Ownership: Each part belongs to exactly one whole at a time. A Room cannot be in two Houses simultaneously.
Lifecycle Dependency: The part's lifecycle is completely controlled by the whole. Parts are created when the whole is created (or later, by the whole) and destroyed when the whole is destroyed.
No Sharing: Unlike aggregation, parts cannot be shared. If you need to share a part, it's aggregation, not composition.
Physical Containment Semantics: Composition often implies that the whole physically "contains" the parts, not just references them.
Cascading Destruction: Deleting the whole automatically deletes all parts. This must be enforced in both code and database schemas.
| Aspect | Composition | Aggregation |
|---|---|---|
| Ownership | Exclusive—part belongs to one whole | Shared possible—part can belong to multiple |
| Part Lifecycle | Dependent—part dies with whole | Independent—part survives whole |
| Part Creation | Whole creates parts | Parts exist before, passed to whole |
| UML Symbol | Filled diamond ◆ | Hollow diamond ◇ |
| Typical Implementation | Parts embedded or created by whole | Parts received as references |
| Delete Behavior | Cascade—delete parts with whole | No cascade—parts remain |
Composition appears when parts are inseparable from their wholes. Here are canonical examples:
A good test for composition: "Does the part have independent identity and meaning without the whole?" An OrderLineItem for "2 widgets at $5 each" is meaningless without knowing which Order it belongs to. A Room without a House is just architectural nonsense. If the part's identity is derived from the whole, it's composition.
Gray Area Examples:
| Scenario | Analysis | Recommendation |
|---|---|---|
| Car ◆— Engine | Engines can theoretically be transplanted, but typically treated as exclusive | Often modeled as composition in practice |
| Company ◆— Department | Departments typically don't outlive the company | Composition, unless modeling mergers |
| Browser ◆— Tab | Tabs don't transfer between browser windows | Composition |
| Email ◆— Attachment | Attachments stored only with that email | Composition |
In UML class diagrams, composition is shown with a filled (solid) diamond at the "whole" end of the relationship line.
┌──────────────┐ ┌──────────────┐
│ House │ ◆─────────── │ Room │
└──────────────┘ 1..* └──────────────┘
Whole Part
┌──────────────┐ ┌──────────────────┐
│ Order │ ◆─────────── │ OrderLineItem │
└──────────────┘ 0..* └──────────────────┘
Reading the Diagram:
Implied Constraints:
When you see a filled diamond, the following semantics are implied:
Composition almost always implies navigation from whole to parts (House can access its Rooms). Navigation from part to whole (Room.getHouse()) is often useful but isn't required. If you add it, be careful about circular references in serialization and garbage collection.
Composition requires careful implementation to enforce the semantics of exclusive ownership and lifecycle dependency.
1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798
// COMPOSITION: Order ◆— OrderLineItem// OrderLineItems are created by Order, belong exclusively to Order,// and are destroyed when Order is destroyed public class OrderLineItem { private final String productId; private final String productName; private final int quantity; private final Money unitPrice; // Package-private constructor: only Order can create line items OrderLineItem(String productId, String productName, int quantity, Money unitPrice) { this.productId = productId; this.productName = productName; this.quantity = quantity; this.unitPrice = unitPrice; } public Money getSubtotal() { return unitPrice.multiply(quantity); } // No setters—line items are typically immutable parts public String getProductId() { return productId; } public String getProductName() { return productName; } public int getQuantity() { return quantity; } public Money getUnitPrice() { return unitPrice; } // Note: No reference back to Order needed unless required} public class Order { private final String orderId; private final String customerId; private final LocalDateTime createdAt; private final List<OrderLineItem> lineItems; // Composition: owned parts private OrderStatus status; public Order(String orderId, String customerId) { this.orderId = orderId; this.customerId = customerId; this.createdAt = LocalDateTime.now(); this.lineItems = new ArrayList<>(); this.status = OrderStatus.DRAFT; } // The Order creates its own line items—enforcing composition public void addItem(String productId, String productName, int quantity, Money unitPrice) { if (status != OrderStatus.DRAFT) { throw new IllegalStateException("Cannot modify submitted order"); } // Order creates the line item—the item doesn't exist before this OrderLineItem item = new OrderLineItem(productId, productName, quantity, unitPrice); lineItems.add(item); } public void removeItem(int index) { if (status != OrderStatus.DRAFT) { throw new IllegalStateException("Cannot modify submitted order"); } lineItems.remove(index); // The removed line item is now garbage—it has no meaning } // Return unmodifiable view—outsiders can't modify the composition public List<OrderLineItem> getLineItems() { return Collections.unmodifiableList(lineItems); } public Money getTotal() { return lineItems.stream() .map(OrderLineItem::getSubtotal) .reduce(Money.ZERO, Money::add); } // When Order is "deleted", its line items are implicitly destroyed // In Java, this happens via garbage collection when Order is unreachable // In databases, this requires explicit cascading} // USAGE: Line items only exist through Orderpublic class CompositionDemo { public static void main(String[] args) { Order order = new Order("O-001", "C-123"); // Line items are created by the Order order.addItem("PROD-1", "Widget", 5, Money.of(10.00)); order.addItem("PROD-2", "Gadget", 2, Money.of(25.00)); // Cannot create line items independently: // OrderLineItem item = new OrderLineItem(...); // Package-private! System.out.println("Total: " + order.getTotal()); // $100.00 // When order goes out of scope and is garbage collected, // its line items are collected too—they can't exist without it }}Key Implementation Patterns:
Restrict Part Construction: Make part constructors package-private or private, and provide factory methods in the whole. This prevents independent part creation.
Whole Creates Parts: Parts are created through methods on the whole, not passed in from outside.
Immutable Parts: Often, composed parts are immutable. Once created, they don't change.
No Sharing Allowed: Don't provide methods that "transfer" a part from one whole to another.
Encapsulated Collection: Always return unmodifiable views of the parts collection.
The defining characteristic of composition is lifecycle dependency. Getting this right requires careful management:
1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980
// Advanced lifecycle management with resource cleanuppublic class Document implements Closeable { private final String documentId; private final List<Paragraph> paragraphs; private final List<Image> embeddedImages; private final Map<String, AttachedFile> attachments; public Document(String documentId) { this.documentId = documentId; this.paragraphs = new ArrayList<>(); this.embeddedImages = new ArrayList<>(); this.attachments = new HashMap<>(); } // Creation: Document creates its parts public Paragraph addParagraph(String content) { Paragraph p = new Paragraph(this, paragraphs.size(), content); paragraphs.add(p); return p; } public Image embedImage(byte[] imageData, String format) { Image img = new Image(this, imageData, format); embeddedImages.add(img); return img; } // Modification: Through the whole public void updateParagraph(int index, String newContent) { paragraphs.get(index).setContent(newContent); } // Removal: Part becomes garbage public void removeParagraph(int index) { Paragraph removed = paragraphs.remove(index); // 'removed' is now unreachable if no other references exist // In manual memory languages, would call: removed.dispose(); } // Destruction: Clean up all parts when closing @Override public void close() { // If parts hold resources, release them for (Image img : embeddedImages) { img.releaseResources(); // Release image buffers } embeddedImages.clear(); for (AttachedFile file : attachments.values()) { file.deleteTemporaryFile(); // Clean up temp files } attachments.clear(); paragraphs.clear(); // Now all parts are cleaned up when document is closed }} // Part with back-reference to the wholeclass Paragraph { private final Document document; // Reference to owning whole private int position; private String content; Paragraph(Document document, int position, String content) { this.document = document; this.position = position; this.content = content; } // Package-private setter—only Document can modify void setContent(String content) { this.content = content; } public Document getDocument() { return document; // Allows navigation from part to whole }}The cascading delete semantics of composition must be enforced in the database schema.
12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849
-- COMPOSITION: Orders and OrderLineItems-- Line items cannot exist without an order -- Orders table: The "whole"CREATE TABLE orders ( order_id VARCHAR(36) PRIMARY KEY, customer_id VARCHAR(36) NOT NULL REFERENCES customers(customer_id), created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, status VARCHAR(20) DEFAULT 'DRAFT'); -- OrderLineItems table: The "parts"CREATE TABLE order_line_items ( line_item_id VARCHAR(36) PRIMARY KEY, order_id VARCHAR(36) NOT NULL REFERENCES orders(order_id) ON DELETE CASCADE, -- ^^^^^^^^^ CRITICAL: ON DELETE CASCADE enforces composition -- When order is deleted, all line items are automatically deleted product_id VARCHAR(36) NOT NULL, product_name VARCHAR(255) NOT NULL, quantity INTEGER NOT NULL CHECK (quantity > 0), unit_price DECIMAL(10,2) NOT NULL, -- The line_item_id might even be a composite key: -- PRIMARY KEY (order_id, line_number) line_number INTEGER NOT NULL); -- Index for efficient access to parts from wholeCREATE INDEX idx_line_items_order ON order_line_items(order_id); -- ALTERNATIVE: Use composite primary key (emphasizes composition)CREATE TABLE invoice_items ( invoice_id VARCHAR(36) NOT NULL REFERENCES invoices(invoice_id) ON DELETE CASCADE, item_number INTEGER NOT NULL, description VARCHAR(255), amount DECIMAL(10,2), PRIMARY KEY (invoice_id, item_number) -- Part ID includes whole ID); -- Compare with AGGREGATION (Department-Professor)-- No ON DELETE CASCADE to the parts:CREATE TABLE department_professors ( department_id VARCHAR(36) REFERENCES departments(department_id) ON DELETE CASCADE, -- ^^^^^^^^^ This cascades the RELATIONSHIP, not the professor professor_id VARCHAR(36) REFERENCES professors(professor_id), -- ^^^^^^^^^ NO CASCADE here—professors survive department deletion PRIMARY KEY (department_id, professor_id));When you see a primary key that includes the parent's ID (like invoice_id + item_number), it often indicates composition. The part's identity is derived from the whole's identity. This pattern makes it impossible for the part to exist without the whole.
Never do this: return this.lineItems; This exposes your internal list, allowing order.getLineItems().clear() to break your Order. Always return Collections.unmodifiableList(lineItems) or a defensive copy.
With all four relationship types covered, here's a comprehensive decision framework:
| Question | Dependency | Association | Aggregation | Composition |
|---|---|---|---|---|
| Is there a whole-part relationship? | No | No | Yes | Yes |
| Does B survive if A is destroyed? | Yes | Yes | Yes | No |
| Can B belong to multiple A's? | N/A | Maybe | Yes | No |
| Does A store a reference to B? | No | Yes | Yes | Yes |
| Does A create B? | No | Sometimes | Rarely | Usually |
| Is the relationship transient? | Yes | No | No | No |
Decision Flowchart:
Start: Does A need to know about B?
│
▼
┌───────────────┐
│ Is it just for│ YES ┌────────────┐
│ a method call?├─────────────►│ DEPENDENCY │
└───────┬───────┘ └────────────┘
│ NO
▼
┌───────────────┐ NO ┌─────────────┐
│ Is there a ├─────────────►│ ASSOCIATION │
│ whole-part? │ └─────────────┘
└───────┬───────┘
│ YES
▼
┌───────────────┐ YES ┌─────────────┐
│ Can parts be ├─────────────►│ AGGREGATION │
│ shared/survive│ └─────────────┘
└───────┬───────┘
│ NO
▼
┌─────────────┐
│ COMPOSITION │
└─────────────┘
Composition is the strongest whole-part relationship, representing exclusive ownership and lifecycle dependency.
Module Complete!
You've now mastered the four fundamental relationship types in object-oriented design:
| Relationship | Coupling | Lifecycle | Use When |
|---|---|---|---|
| Dependency | Weakest | Independent | Temporary method-level usage |
| Association | Light | Independent | Objects that know each other |
| Aggregation | Medium | Independent | Whole-part with sharing |
| Composition | Strong | Dependent | Whole-part with exclusive ownership |
This knowledge forms the foundation for modeling any domain accurately and implementing systems with correct lifecycle management.
You now command the complete relationship vocabulary of object-oriented design. You can analyze domains, identify correct relationship types, represent them in UML diagrams, implement them in code, and model them in databases. This is foundational knowledge that will inform every system you design.