Marker Interface in Java – Tagging Classes with Empty Interfaces
A marker interface (or tagging interface) is an interface with no methods and no constants. Implementing it does not add behaviour; it simply marks a class as having a certain property. Other code, such as the JVM, a library or your own framework, checks for the marker with instanceof and treats marked objects differently.
This tutorial covers the JDK's marker interfaces (Serializable, Cloneable, RandomAccess), how to create your own, how marker interfaces compare with marker annotations, and how sealed interfaces and pattern matching (Java 17 to 25) change the way modern Java code tags and handles types.
1. What Is a Marker Interface?
public interface Serializable { } // the entire declaration in java.io
class Customer implements Serializable { ... } // "Customer objects may be serialized"
The marker carries meaning through the type system: an object is either an instance of the marker type or not. Code that cares checks obj instanceof Marker or declares parameters of the marker type.
2. Marker Interfaces in the JDK
| Marker | Package | Meaning | Checked by |
|---|---|---|---|
Serializable | java.io | Objects may be converted to a byte stream | ObjectOutputStream throws NotSerializableException otherwise |
Cloneable | java.lang | Object.clone() may make a field-by-field copy | Object.clone() throws CloneNotSupportedException otherwise |
RandomAccess | java.util | The list supports fast indexed access | Algorithms such as Collections.binarySearch choose a strategy |
Remote | java.rmi | Methods may be invoked remotely via RMI | The RMI runtime |
EventListener | java.util | Tags all event listener interfaces | Tools and frameworks |
import java.io.*;
import java.util.*;
public class JdkMarkers {
static class Plain { int x = 1; }
static class Saveable implements Serializable {
@Serial
private static final long serialVersionUID = 1L; // version tag for the serialized form
int x = 1;
}
static class Sheep implements Cloneable {
String name = "Dolly";
@Override public Sheep clone() throws CloneNotSupportedException { return (Sheep) super.clone(); }
}
static class Goat {
String name = "Billy";
public Goat copy() throws CloneNotSupportedException { return (Goat) super.clone(); }
}
static void serialize(Object o) {
try (ObjectOutputStream out = new ObjectOutputStream(new ByteArrayOutputStream())) {
out.writeObject(o);
System.out.println("Serializable: " + o.getClass().getSimpleName() + " OK");
} catch (IOException e) {
System.out.println("Serializable: " + e);
}
}
public static void main(String[] args) {
serialize(new Saveable());
serialize(new Plain());
try { System.out.println("Cloneable: cloned " + new Sheep().clone().name); }
catch (CloneNotSupportedException e) { System.out.println("Cloneable: " + e); }
try { new Goat().copy(); }
catch (CloneNotSupportedException e) { System.out.println("Cloneable: " + e); }
// RandomAccess tells algorithms that get(i) is fast
System.out.println("ArrayList is RandomAccess: " + (new ArrayList<>() instanceof RandomAccess));
System.out.println("LinkedList is RandomAccess: " + (new LinkedList<>() instanceof RandomAccess));
}
}
Serializable: Saveable OK Serializable: java.io.NotSerializableException: JdkMarkers$Plain Cloneable: cloned Dolly Cloneable: java.lang.CloneNotSupportedException: JdkMarkers$Goat ArrayList is RandomAccess: true LinkedList is RandomAccess: false
The classes are identical except for the marker, yet serialization and cloning succeed only for marked classes. See Serialization and Object Cloning.
3. Creating a Custom Marker Interface
You can define your own marker to tag classes that need special handling, such as events that must be audited:
import java.util.List;
public class CustomMarker {
// Custom marker interface: no methods, just a type tag
interface Auditable { }
record Payment(String id, double amount) implements Auditable { }
record Login(String user) implements Auditable { }
record PageView(String url) { }
static void process(Object event) {
if (event instanceof Auditable auditable) { // check the marker (pattern matching, Java 16+)
System.out.println("AUDIT -> " + auditable);
}
System.out.println("handled " + event.getClass().getSimpleName());
}
// Unlike an annotation, the marker is a TYPE: the compiler can enforce it
static void archive(Auditable record) {
System.out.println("ARCHIVE -> " + record);
}
public static void main(String[] args) {
List<Object> events = List.of(new Payment("P-1", 999.0), new PageView("/java"), new Login("asha"));
events.forEach(CustomMarker::process);
archive(new Login("ravi"));
// archive(new PageView("/x")); // compile error: PageView is not Auditable
}
}
AUDIT -> Payment[id=P-1, amount=999.0] handled Payment handled PageView AUDIT -> Login[user=asha] handled Login ARCHIVE -> Login[user=ravi]
The key strength of a marker interface is visible in archive(Auditable record): the compiler rejects unmarked objects, so mistakes are caught before the program runs. The check in process uses pattern matching for instanceof (final since Java 16), which tests the marker and gives you a typed variable in one step.
4. Marker Interfaces vs Marker Annotations
Annotations, added in Java 5, offer another way to tag code. An annotation with no elements is called a marker annotation; examples in the JDK are @Override, @FunctionalInterface (Java 8) and @SafeVarargs. (@Deprecated is no longer one: since Java 9 it has the since and forRemoval elements.)
import java.lang.annotation.*;
import java.util.List;
public class MarkerAnnotation {
// Annotation-based alternative: a marker annotation
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@interface Cacheable { }
@Cacheable static class ProductService { }
static class OrderService { }
public static void main(String[] args) {
for (Class<?> c : List.of(ProductService.class, OrderService.class)) {
boolean cache = c.isAnnotationPresent(Cacheable.class);
System.out.println(c.getSimpleName() + " cacheable? " + cache);
}
}
}
ProductService cacheable? true OrderService cacheable? false
| Aspect | Marker interface | Marker annotation |
|---|---|---|
| Defines a type | Yes: usable for parameters and variables | No |
| Compile-time checking | Yes (method can require the marker type) | Only with annotation processors |
| Inherited by subclasses | Always | Only if @Inherited (and only for classes) |
| Can mark | Classes and interfaces only | Classes, methods, fields, parameters, packages, modules… |
| Can carry data | No | Yes, by adding elements later |
| Checked with | instanceof | Reflection: isAnnotationPresent |
A useful rule (from Effective Java): use a marker interface when you want to define a type that methods can accept; use a marker annotation to mark program elements other than classes, or in frameworks that already rely heavily on annotations (such as Spring and JPA).
5. Modern Java: Sealed Interfaces and Pattern Matching
A marker interface is open: any class can implement it, so code that checks the marker can never be sure it has seen every kind of object. When you control the full set of types and want the compiler to check that each one is handled, modern Java offers a better tool: a sealed interface (final in Java 17) combined with pattern matching for switch and record patterns (both final in Java 21).
import java.util.List;
public class SealedEvents {
// Sealed interface: only the listed types may implement it (Java 17+)
sealed interface Event permits Payment, Login, PageView { }
record Payment(String id, double amount) implements Event { }
record Login(String user) implements Event { }
record PageView(String url) implements Event { }
// Exhaustive switch with record patterns (Java 21+): no default branch needed
static String describe(Event event) {
return switch (event) {
case Payment(String id, double amount) when amount > 500 -> "AUDIT large payment " + id;
case Payment(String id, double amount) -> "payment " + id + " for " + amount;
case Login(String user) -> "AUDIT login by " + user;
case PageView(String url) -> "page view " + url;
};
}
public static void main(String[] args) {
List<Event> events = List.of(new Payment("P-1", 999.0), new Payment("P-2", 45.0),
new Login("asha"), new PageView("/java"));
events.forEach(e -> System.out.println(describe(e)));
}
}
AUDIT large payment P-1 payment P-2 for 45.0 AUDIT login by asha page view /java
The switch has no default branch because the compiler knows every permitted Event type. If you add a new record to the permits list and forget to handle it, compilation fails with "the switch expression does not cover all possible input values". A marker check with instanceof gives no such guarantee.
| Use | When |
|---|---|
| Marker interface | An open set of classes shares a property, and methods should accept only marked types (for example Serializable) |
| Marker annotation | You need to tag methods, fields or other non-type elements, or a framework reads annotations |
| Sealed interface + pattern matching | You own a fixed set of types and want exhaustive, compiler-checked handling of each one |
Marker interfaces themselves have not changed in Java 25: Serializable, Cloneable, RandomAccess and the others work exactly as in earlier releases. The examples on this page use only features that are final in Java 21 and run unchanged on Java 25 (LTS). See Java 17 features and Java 21 features for more on sealed types and pattern matching.
6. Criticism and Pitfalls
- Inheritance cannot be undone: if a parent class is
Serializable, every subclass is too, even if it should not be. Cloneableis a poor design: it does not declareclone(), so the marker alone does not make an object cloneable through the interface. Copy constructors or factory methods are usually better.- Meaning is implicit: nothing in the interface explains what it promises; document it clearly.
Serializablecarries security risk: deserializing untrusted data can run unexpected code. If you must deserialize, set anObjectInputFilter(Java 9+) to allow only expected classes; Java 17 added JVM-wide filter factories. For new code, prefer formats such as JSON with explicit mapping.- Declare
serialVersionUID: mark it with@Serial(Java 14+) so the compiler checks its declaration, as shown inJdkMarkers.java.
7. Summary
- A marker interface has no methods and tags a class with a property.
- JDK markers include
Serializable,CloneableandRandomAccess. - Markers are checked with
instanceofor enforced by parameter types at compile time. - Marker annotations are more flexible but do not define a type.
- For a fixed set of types, a sealed interface with pattern matching (Java 17–21, unchanged in Java 25) gives compiler-checked, exhaustive handling.
Related Tutorials
Frequently Asked Questions (FAQ)
Q1. What is a marker interface in Java?
An interface with no methods or fields whose only purpose is to mark classes that implement it, so other code can treat them specially.
Q2. What are examples of marker interfaces?
Serializable, Cloneable, RandomAccess, Remote and EventListener.
Q3. Why is it called a tagging interface?
Because implementing it only attaches a tag to the class; it adds no methods to implement.
Q4. How does Java use Serializable?
The serialization library, not the JVM itself, checks the marker: ObjectOutputStream tests whether an object is Serializable and throws NotSerializableException if it is not.
Q5. What happens if I call clone() without implementing Cloneable?
Object.clone() throws CloneNotSupportedException.
Q6. What is the RandomAccess interface?
A marker indicating that a list supports fast (constant-time) indexed access, like ArrayList. Algorithms use it to choose between index-based loops and iterators.
Q7. Can I create my own marker interface?
Yes. Declare an empty interface, implement it on the relevant classes, and check it with instanceof or use it as a parameter type.
Q8. What is the difference between a marker interface and a marker annotation?
A marker interface defines a type and enables compile-time checks; a marker annotation can mark more kinds of elements and is checked by reflection or annotation processors.
Q9. Are marker interfaces still used?
Yes, but less often. Annotations replaced many uses, while marker interfaces remain valuable when a type is needed for compile-time safety.
Q10. Is a functional interface a marker interface?
No. A functional interface has exactly one abstract method. @FunctionalInterface is a marker annotation, not a marker interface.
Q11. Is Runnable a marker interface?
No. It declares the run() method.
Q12. Do subclasses inherit a marker interface?
Yes. If a class implements a marker, all its subclasses are instances of the marker type too.
Q13. Can a marker interface be removed in a subclass?
No. Once a superclass implements an interface, subclasses cannot "un-implement" it. For Serializable, a subclass can only throw an exception from writeObject to block serialization.
Q14. Why is Cloneable considered badly designed?
It has no clone() method, so you cannot call clone() through a Cloneable reference, and it changes the behaviour of a protected method in Object in a hidden way.
Q15. Can a marker interface have constants?
A pure marker has none. An interface with constants is technically no longer an empty marker, though it would still add no methods.
Q16. How do I check whether an object has a marker?
Use obj instanceof Marker, or Marker.class.isAssignableFrom(clazz) for a Class object.
Q17. Is Remote a marker interface?
Yes. java.rmi.Remote marks interfaces whose methods can be invoked from another JVM through RMI.
Q18. What is the advantage of a marker interface over a boolean field?
It is checked by the type system, applies to all instances of a class without extra state, and can be enforced by method signatures at compile time.
Q19. Are records Serializable by default?
No. A record must implement Serializable explicitly, like any class. Records then serialize safely through their canonical constructor.
Q20. Do sealed interfaces replace marker interfaces?
They solve a different problem. A sealed interface (Java 17) restricts which classes may implement it, so a pattern-matching switch (Java 21) can handle every subtype without a default branch. A marker interface stays open to any class. Use sealed types for a closed set you control, and markers for an open property.
Q21. Did Java 25 change marker interfaces?
No. Serializable, Cloneable, RandomAccess and the other markers behave the same in Java 25 (LTS). What changed in recent releases is the alternatives: records, sealed interfaces and pattern matching make many hand-written marker checks unnecessary.