• Home
  • About
    • Thoughts To Pen photo

      Thoughts To Pen

      My thoughts on Computer Programming || Psychology || Personal Finances || & much more...

    • Learn More
    • Twitter
    • Instagram
    • Github
    • StackOverflow
  • Posts
    • All Posts
    • All Tags
  • Projects
  • Portfolio
  • Resources
  • About

Pattern Matching for instanceof (Java 17)

10 Nov 2025

Pattern Matching for instanceof, finalized in Java 16 and included in Java 17 through JEP 394, revolutionizes how we perform type checks and casts in Java. This feature eliminates the redundant casting that traditionally follows an instanceof check, making your code more concise and less error-prone.

The Old Way vs. The New Way

Before Java 17 (Traditional Approach)

public void processObject(Object obj) {
    if (obj instanceof String) {
        String str = (String) obj;  // Redundant cast
        System.out.println("String length: " + str.length());
    }
}

With Java 17 (Pattern Matching)

public void processObject(Object obj) {
    if (obj instanceof String str) {  // Type test + binding in one
        System.out.println("String length: " + str.length());
    }
}

The pattern variable str is automatically created and scoped to the if block, eliminating the need for an explicit cast.

How It Works

When you use pattern matching with instanceof, Java performs two operations simultaneously:

  1. Type Test: Checks if the object is an instance of the specified type
  2. Binding: If the test succeeds, creates a new variable of that type
if (obj instanceof String str) {
    // 'str' is available here and is of type String
    System.out.println(str.toUpperCase());
}
// 'str' is NOT available here (out of scope)

Scope Rules

The pattern variable is only in scope where the compiler can guarantee the instanceof check succeeded:

public void demonstrateScope(Object obj) {
    // Pattern variable in if statement
    if (obj instanceof String str) {
        System.out.println(str.length());  // ✓ str is in scope
    }
    // System.out.println(str.length());  // ✗ Compile error: str out of scope
    
    // Pattern variable with logical AND
    if (obj instanceof String str && str.length() > 5) {
        System.out.println("Long string: " + str);  // ✓ str is in scope
    }
    
    // Pattern variable with logical OR
    if (obj instanceof String str || str.isEmpty()) {  // ✗ Compile error
        // str might not be initialized if first condition is false
    }
    
    // Pattern variable with negation
    if (!(obj instanceof String str)) {
        // System.out.println(str);  // ✗ str is NOT in scope here
    } else {
        System.out.println(str.toUpperCase());  // ✓ str IS in scope here
    }
}

Real-World Example: Processing Different Message Types

public sealed interface Message permits TextMessage, ImageMessage, VideoMessage {}

public record TextMessage(String content, String sender) implements Message {}
public record ImageMessage(byte[] data, String caption, String sender) implements Message {}
public record VideoMessage(byte[] data, int duration, String sender) implements Message {}

public class MessageProcessor {
    
    public void processMessage(Message message) {
        if (message instanceof TextMessage txt) {
            System.out.println("Text from " + txt.sender() + ": " + txt.content());
            
        } else if (message instanceof ImageMessage img) {
            System.out.println("Image from " + img.sender());
            System.out.println("Caption: " + img.caption());
            System.out.println("Size: " + img.data().length + " bytes");
            
        } else if (message instanceof VideoMessage vid) {
            System.out.println("Video from " + vid.sender());
            System.out.println("Duration: " + vid.duration() + " seconds");
            System.out.println("Size: " + vid.data().length + " bytes");
        }
    }
    
    public String getMessageSummary(Message message) {
        if (message instanceof TextMessage txt && txt.content().length() > 100) {
            return "Long text message from " + txt.sender();
        } else if (message instanceof TextMessage txt) {
            return "Text: " + txt.content();
        } else if (message instanceof ImageMessage img) {
            return "Image: " + img.caption();
        } else if (message instanceof VideoMessage vid) {
            return "Video (" + vid.duration() + "s)";
        }
        return "Unknown message type";
    }
}

Combining with Logical Operators

Pattern matching works seamlessly with logical operators:

public void validateInput(Object input) {
    // Using AND to add additional conditions
    if (input instanceof String str && !str.isEmpty() && str.length() < 100) {
        System.out.println("Valid string: " + str);
    }
    
    // Using AND with multiple pattern variables
    if (input instanceof String str && str.matches("\\d+")) {
        int number = Integer.parseInt(str);
        System.out.println("Parsed number: " + number);
    }
}

Practical Example: Shape Area Calculator

public abstract class Shape {
    public abstract double area();
}

public class Circle extends Shape {
    private final double radius;
    
    public Circle(double radius) {
        this.radius = radius;
    }
    
    public double getRadius() {
        return radius;
    }
    
    @Override
    public double area() {
        return Math.PI * radius * radius;
    }
}

public class Rectangle extends Shape {
    private final double width;
    private final double height;
    
    public Rectangle(double width, double height) {
        this.width = width;
        this.height = height;
    }
    
    public double getWidth() {
        return width;
    }
    
    public double getHeight() {
        return height;
    }
    
    @Override
    public double area() {
        return width * height;
    }
}

public class ShapeAnalyzer {
    
    public String analyzeShape(Shape shape) {
        if (shape instanceof Circle c && c.getRadius() > 10) {
            return "Large circle with area: " + c.area();
            
        } else if (shape instanceof Circle c) {
            return "Small circle with area: " + c.area();
            
        } else if (shape instanceof Rectangle r && r.getWidth() == r.getHeight()) {
            return "Square with area: " + r.area();
            
        } else if (shape instanceof Rectangle r) {
            return "Rectangle with area: " + r.area();
        }
        
        return "Unknown shape";
    }
    
    public double calculatePerimeter(Shape shape) {
        if (shape instanceof Circle c) {
            return 2 * Math.PI * c.getRadius();
        } else if (shape instanceof Rectangle r) {
            return 2 * (r.getWidth() + r.getHeight());
        }
        return 0;
    }
}

Benefits

1. Reduced Boilerplate

No more redundant casting after instanceof checks.

2. Improved Readability

The intent is clearer when the type test and binding happen together.

3. Fewer Errors

Eliminates the possibility of casting to the wrong type after an instanceof check.

4. Better Performance

The JVM only performs the type check once, not twice (once for instanceof and once for the cast).

Common Patterns

Null-Safe Processing

public int getStringLength(Object obj) {
    if (obj instanceof String str) {
        return str.length();
    }
    return 0;  // null or not a String
}

Nested Conditions

public void processData(Object data) {
    if (data instanceof List<?> list && !list.isEmpty()) {
        if (list.get(0) instanceof String firstStr) {
            System.out.println("First element: " + firstStr);
        }
    }
}

Early Returns

public String formatValue(Object value) {
    if (!(value instanceof Number num)) {
        return "Not a number";
    }
    
    // num is in scope for the rest of the method
    return String.format("%.2f", num.doubleValue());
}

Conclusion

Pattern Matching for instanceof is a simple yet powerful feature that makes Java code more concise and readable. By combining type testing with variable binding, it eliminates redundant casts and reduces the potential for errors. This feature works particularly well with sealed classes and modern Java patterns, making your code both safer and more expressive.



programmingjavajava17 Share Tweet Msg