
Flyweight Design Pattern in Java: Explained
The Flyweight design pattern is a structural pattern that helps minimize memory usage by sharing common data among multiple objects efficiently. When you’re dealing with large numbers of objects that share similar characteristics, this pattern can dramatically reduce memory consumption and improve application performance. This post will walk you through understanding the Flyweight pattern’s mechanics, implementing it in Java with practical examples, and applying it to real-world scenarios where object overhead becomes a significant concern.
How the Flyweight Pattern Works
The Flyweight pattern separates object data into two categories: intrinsic and extrinsic state. Intrinsic state is shared among multiple objects and stored in the flyweight, while extrinsic state is unique to each object and passed as parameters to flyweight methods.
The pattern typically involves these key components:
- Flyweight Interface: Defines methods that accept extrinsic state as parameters
- Concrete Flyweight: Implements the flyweight interface and stores intrinsic state
- Flyweight Factory: Manages flyweight instances and ensures sharing
- Context: Contains extrinsic state and references to flyweight objects
Here’s the basic structure:
public interface Flyweight {
void operation(String extrinsicState);
}
public class ConcreteFlyweight implements Flyweight {
private final String intrinsicState;
public ConcreteFlyweight(String intrinsicState) {
this.intrinsicState = intrinsicState;
}
@Override
public void operation(String extrinsicState) {
System.out.println("Intrinsic: " + intrinsicState +
", Extrinsic: " + extrinsicState);
}
}
Step-by-Step Implementation Guide
Let’s build a practical example using a text editor scenario where we need to render thousands of characters efficiently.
Step 1: Define the Flyweight Interface
public interface CharacterFlyweight {
void render(int x, int y, String font, int size);
}
Step 2: Create Concrete Flyweight Classes
public class Character implements CharacterFlyweight {
private final char symbol; // Intrinsic state
public Character(char symbol) {
this.symbol = symbol;
// Simulate expensive object creation
try {
Thread.sleep(1);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
@Override
public void render(int x, int y, String font, int size) {
System.out.printf("Rendering '%c' at (%d,%d) with %s font, size %d%n",
symbol, x, y, font, size);
}
public char getSymbol() {
return symbol;
}
}
Step 3: Implement the Flyweight Factory
import java.util.HashMap;
import java.util.Map;
public class CharacterFactory {
private static final Map flyweights = new HashMap<>();
private static int instanceCount = 0;
public static Character getCharacter(char symbol) {
Character character = flyweights.get(symbol);
if (character == null) {
character = new Character(symbol);
flyweights.put(symbol, character);
instanceCount++;
System.out.println("Created new flyweight for: " + symbol +
" (Total instances: " + instanceCount + ")");
}
return character;
}
public static int getInstanceCount() {
return instanceCount;
}
public static void printStatistics() {
System.out.println("Total flyweight instances created: " + instanceCount);
System.out.println("Unique characters: " + flyweights.keySet());
}
}
Step 4: Create Context Classes
import java.util.ArrayList;
import java.util.List;
public class TextDocument {
private List characters = new ArrayList<>();
public void addCharacter(char symbol, int x, int y, String font, int size) {
CharacterFlyweight character = CharacterFactory.getCharacter(symbol);
characters.add(new CharacterContext(character, x, y, font, size));
}
public void render() {
for (CharacterContext context : characters) {
context.render();
}
}
public int getCharacterCount() {
return characters.size();
}
private static class CharacterContext {
private final CharacterFlyweight character;
private final int x, y, size; // Extrinsic state
private final String font; // Extrinsic state
public CharacterContext(CharacterFlyweight character, int x, int y,
String font, int size) {
this.character = character;
this.x = x;
this.y = y;
this.font = font;
this.size = size;
}
public void render() {
character.render(x, y, font, size);
}
}
}
Step 5: Demonstration and Testing
public class FlyweightDemo {
public static void main(String[] args) {
TextDocument document = new TextDocument();
String text = "Hello World! This is a flyweight pattern demonstration.";
// Add characters to document
for (int i = 0; i < text.length(); i++) {
char c = text.charAt(i);
document.addCharacter(c, i * 10, 20, "Arial", 12);
}
// Add more repetitive text to show sharing benefits
String repeated = "aaaaabbbbbccccc";
for (int i = 0; i < repeated.length(); i++) {
char c = repeated.charAt(i);
document.addCharacter(c, i * 10, 40, "Times", 14);
}
System.out.println("Total characters in document: " + document.getCharacterCount());
CharacterFactory.printStatistics();
// Demonstrate memory efficiency
demonstrateMemoryEfficiency();
}
private static void demonstrateMemoryEfficiency() {
long startTime = System.currentTimeMillis();
// Create document with many repeated characters
TextDocument largeDoc = new TextDocument();
String pattern = "abcdefghij";
for (int repeat = 0; repeat < 1000; repeat++) {
for (int i = 0; i < pattern.length(); i++) {
largeDoc.addCharacter(pattern.charAt(i), i, repeat, "Courier", 10);
}
}
long endTime = System.currentTimeMillis();
System.out.println("\nLarge Document Statistics:");
System.out.println("Characters created: " + largeDoc.getCharacterCount());
System.out.println("Flyweight instances: " + CharacterFactory.getInstanceCount());
System.out.println("Memory efficiency: " +
(largeDoc.getCharacterCount() / (double) CharacterFactory.getInstanceCount()) +
":1 ratio");
System.out.println("Creation time: " + (endTime - startTime) + "ms");
}
}
Real-World Examples and Use Cases
The Flyweight pattern shines in several practical scenarios:
Game Development - Particle Systems
public class Particle implements Flyweight {
private final String texture; // Intrinsic
private final Color color; // Intrinsic
private final ParticleType type; // Intrinsic
public Particle(String texture, Color color, ParticleType type) {
this.texture = texture;
this.color = color;
this.type = type;
}
public void update(double x, double y, double velocity, double direction) {
// Particle behavior using extrinsic state
System.out.printf("Updating %s particle at (%.2f,%.2f) with velocity %.2f%n",
type, x, y, velocity);
}
}
public class ParticleSystem {
private List particles = new ArrayList<>();
public void createExplosion(double centerX, double centerY) {
Particle sparkParticle = ParticleFactory.getParticle("spark.png", Color.YELLOW, ParticleType.SPARK);
for (int i = 0; i < 100; i++) {
double angle = Math.random() * 2 * Math.PI;
double velocity = Math.random() * 50;
particles.add(new ParticleContext(sparkParticle, centerX, centerY, velocity, angle));
}
}
}
Web Applications - Icon Management
public class WebIcon implements Flyweight {
private final String iconData; // Intrinsic - SVG or image data
private final IconType type; // Intrinsic
public WebIcon(String iconData, IconType type) {
this.iconData = iconData;
this.type = type;
}
public String render(int size, String color, String cssClass) {
return String.format("%s",
cssClass, size, color, iconData);
}
}
public class IconFactory {
private static final Map icons = new HashMap<>();
public static WebIcon getIcon(String name, IconType type) {
String key = name + "_" + type;
return icons.computeIfAbsent(key, k -> {
String iconData = loadIconData(name, type);
return new WebIcon(iconData, type);
});
}
private static String loadIconData(String name, IconType type) {
// Simulate loading icon data from resources
return "" + Integer.toHexString(name.hashCode() % 0xFFFF) + ";";
}
}
Performance Comparisons
Here's a comparison showing the memory benefits of using the Flyweight pattern:
Scenario | Without Flyweight | With Flyweight | Memory Savings |
---|---|---|---|
10,000 characters (26 unique) | 10,000 objects | 26 flyweights + 10,000 contexts | ~99.7% object reduction |
Text document (500 unique chars) | 50,000 objects | 500 flyweights + 50,000 contexts | ~99% object reduction |
Game particles (5 types, 10,000 instances) | 10,000 objects | 5 flyweights + 10,000 contexts | ~99.95% object reduction |
Performance benchmark results from testing with different object counts:
public class PerformanceBenchmark {
public static void benchmarkFlyweight() {
int[] testSizes = {1000, 10000, 100000};
for (int size : testSizes) {
System.out.println("\nTesting with " + size + " objects:");
// Without flyweight
long start = System.currentTimeMillis();
List regular = new ArrayList<>();
for (int i = 0; i < size; i++) {
char c = (char) ('a' + (i % 26));
regular.add(new NonFlyweightCharacter(c, i, 10, "Arial", 12));
}
long regularTime = System.currentTimeMillis() - start;
// With flyweight
start = System.currentTimeMillis();
TextDocument flyweightDoc = new TextDocument();
for (int i = 0; i < size; i++) {
char c = (char) ('a' + (i % 26));
flyweightDoc.addCharacter(c, i, 10, "Arial", 12);
}
long flyweightTime = System.currentTimeMillis() - start;
System.out.println("Regular approach: " + regularTime + "ms");
System.out.println("Flyweight approach: " + flyweightTime + "ms");
System.out.println("Performance improvement: " +
((regularTime - flyweightTime) * 100.0 / regularTime) + "%");
}
}
}
Comparisons with Alternative Patterns
Pattern | Primary Goal | When to Use | Memory Impact | Complexity |
---|---|---|---|---|
Flyweight | Minimize memory usage | Many similar objects | High reduction | Medium |
Singleton | Single instance | One shared object needed | Minimal usage | Low |
Object Pool | Reuse expensive objects | Object creation is costly | Controlled usage | Medium |
Prototype | Clone objects efficiently | Complex object creation | Depends on cloning | Low-Medium |
Flyweight vs Object Pool:
// Object Pool - manages object lifecycle
public class ConnectionPool {
private Queue pool = new LinkedList<>();
public DatabaseConnection getConnection() {
if (pool.isEmpty()) {
return new DatabaseConnection();
}
return pool.poll();
}
public void releaseConnection(DatabaseConnection conn) {
pool.offer(conn);
}
}
// Flyweight - shares immutable state
public class CharacterFlyweight {
private final char symbol; // Shared immutable state
public void render(Position position, Font font) {
// Uses external state
}
}
Best Practices and Common Pitfalls
Best Practices:
- Immutability: Keep flyweight objects immutable to ensure thread safety
- Factory Management: Use a centralized factory to control flyweight creation
- State Separation: Clearly distinguish between intrinsic and extrinsic state
- Memory Monitoring: Track the actual memory benefits in your specific use case
public class ThreadSafeFlyweightFactory {
private static final ConcurrentHashMap flyweights =
new ConcurrentHashMap<>();
public static Flyweight getFlyweight(String key) {
return flyweights.computeIfAbsent(key, k -> {
// Thread-safe creation
return new ConcreteFlyweight(k);
});
}
}
Common Pitfalls to Avoid:
- Mutable Flyweights: Never modify flyweight internal state after creation
- Excessive Extrinsic State: Too much external state can negate memory benefits
- Premature Optimization: Only use when you have confirmed memory issues
- Complex State Management: Don't make extrinsic state management overly complicated
// BAD: Mutable flyweight
public class BadFlyweight {
private String sharedData;
public void setSharedData(String data) { // Don't do this!
this.sharedData = data;
}
}
// GOOD: Immutable flyweight
public class GoodFlyweight {
private final String sharedData;
public GoodFlyweight(String sharedData) {
this.sharedData = sharedData;
}
public void operation(String extrinsicData) {
// Use both intrinsic and extrinsic data
}
}
Memory Leak Prevention:
public class LeakPreventionFactory {
private static final Map> flyweights =
new ConcurrentHashMap<>();
public static Flyweight getFlyweight(String key) {
WeakReference ref = flyweights.get(key);
Flyweight flyweight = (ref != null) ? ref.get() : null;
if (flyweight == null) {
flyweight = new ConcreteFlyweight(key);
flyweights.put(key, new WeakReference<>(flyweight));
}
return flyweight;
}
// Cleanup method for long-running applications
public static void cleanup() {
flyweights.entrySet().removeIf(entry -> entry.getValue().get() == null);
}
}
Integration with Modern Java Features:
// Using Java 8+ features for cleaner implementation
public class ModernFlyweightFactory {
private static final Map cache = new ConcurrentHashMap<>();
public static Flyweight getFlyweight(String type, String data) {
String key = type + ":" + data;
return cache.computeIfAbsent(key, k ->
FlyweightBuilder.newBuilder()
.withType(type)
.withData(data)
.build()
);
}
// Java 9+ - compact string support awareness
public static void optimizeForCompactStrings(String data) {
// Take advantage of compact string storage when available
return cache.computeIfAbsent(data.intern(), k -> new StringFlyweight(k));
}
}
The Flyweight pattern provides substantial memory benefits when dealing with large numbers of similar objects, but it requires careful design to separate intrinsic and extrinsic state effectively. When implementing flyweights in production applications, especially those running on servers like VPS instances or dedicated servers, monitor memory usage to ensure the pattern delivers the expected benefits. For comprehensive documentation on Java design patterns, refer to the official Oracle Java tutorials and the Refactoring Guru design patterns guide.

This article incorporates information and material from various online sources. We acknowledge and appreciate the work of all original authors, publishers, and websites. While every effort has been made to appropriately credit the source material, any unintentional oversight or omission does not constitute a copyright infringement. All trademarks, logos, and images mentioned are the property of their respective owners. If you believe that any content used in this article infringes upon your copyright, please contact us immediately for review and prompt action.
This article is intended for informational and educational purposes only and does not infringe on the rights of the copyright owners. If any copyrighted material has been used without proper credit or in violation of copyright laws, it is unintentional and we will rectify it promptly upon notification. Please note that the republishing, redistribution, or reproduction of part or all of the contents in any form is prohibited without express written permission from the author and website owner. For permissions or further inquiries, please contact us.