A guide to creating objects in Java
Every way to get an object in Java (new, static factories, builders, reflection, method handles, clone, deserialization), which ones skip your constructor, what HotSpot does on new step by step, TLAB allocation and escape analysis.
On this page
🏭 What does it mean to create an object in Java?
Creating an object means asking the JVM for a fresh block of heap memory shaped like a class, then getting a reference back that points at it. Most of the time you do it with new Order(42, "alice"), but it is not the only door: static factories, builders, clone(), reflection and deserialization all hand you objects too, and some of them never run your constructor.
Interviewers like this topic because it has two layers. The first layer is a list ("what are the ways to create an object?") that most people can recite. The second layer is what separates candidates: which of those paths skip the constructor, what the JVM physically does between new and the reference landing in your variable, and why allocating a million short-lived objects is cheap while keeping them alive is not.
What you’ll learn
- List every creation path and say which run a constructor and which skip it (
clone(), Java deserialization) - Read the bytecode for
new(new,dup,invokespecial <init>) and explain why thedupis there - Walk through the six steps of
new: class init check, TLAB bump allocation, zeroing, header, constructor chain, publish - Create objects reflectively the modern way with
getDeclaredConstructor().newInstance()andMethodHandles.Lookup.findConstructor - Choose between a constructor, a static factory and a builder, and avoid
clone()in new code - Explain what escape analysis and scalar replacement remove, and why allocation is cheap but survivors are not
For this lesson, we'll focus on every way to get an instance, the exact sequence HotSpot follows on new, and the escape analysis optimisation that can make an allocation disappear.
Below the line (out of scope)
- Constructor rules and initialisation order in detail: A guide to constructors in Java
- What a class and an object look like in memory: Java classes and objects
this(...), fluent APIs and leakingthis: The this keyword in Java- Copy semantics of
clone(): Deep copy vs shallow copy in Java - Reflection as a whole: A guide to Java reflection
The 30-second answer
You can create an object with new, with a static factory or builder (which call new internally), with reflection (Constructor.newInstance or a MethodHandle), with clone(), or by deserializing bytes. new, factories, builders and reflection all run a constructor. clone() copies the fields of an existing object without running any constructor, and Java serialization allocates the object and runs only the no-arg constructor of the first non-serializable superclass, so your own constructor's validation is skipped. On new, the JVM makes sure the class is loaded and initialised, bumps a pointer in the thread's TLAB to grab memory, zeroes the fields, writes the object header, runs the constructor chain up to Object, and only then stores the reference. The JIT can remove the allocation entirely if escape analysis proves the object never leaves the method.
Interview radar
Asked very often: What are the different ways to create an object in Java, and which of them do not call the constructor?
Senior rounds probe: What happens step by step when new runs, what a TLAB is, and what escape analysis can and cannot eliminate.
Production angle: An object loaded from a cache or a message queue that breaks an invariant your constructor was supposed to enforce, because deserialization skipped it.
01Picture it first: a car factory with several doors
Think of a car factory. Most cars roll off the main assembly line: raw parts in, every inspection station passed, finished car out. That's new with a constructor. The showroom manager might take orders through a sales desk (a static factory) or a custom configurator (a builder), but both of those still send the car down the main line.
Then there are the side doors. A photocopier for cars (clone()) stamps out an exact copy of an existing car without visiting any inspection station. A warehouse (deserialization) reassembles a car from a crate of labelled parts, and again skips the line. A remote control (reflection) can start the main line from outside, even for models the showroom keeps private.
The map below sorts every creation path by one question that matters in interviews: does your constructor run?
A map of six ways to create an Order: new, static factory, builder and reflection all funnel into the constructor and its validation; clone copies fields from an existing object and Java deserialization fills fields from bytes, both skipping the Order constructor
Notice three things:
- Four of the six paths end at the same constructor. Factories and builders are API design choices, not new JVM mechanisms.
- The two side doors skip your constructor. That's where invariants quietly break.
- Reflection is not a side door for construction. It finds a constructor at run time, but it still runs it.
02Object creation under the hood
Let's use a small value class from a payments service. It validates in its constructor, which makes it easy to see which paths respect that check.
public final class Payment implements Cloneable, java.io.Serializable {
private final String id;
private final long amountCents;
public Payment(String id, long amountCents) {
if (amountCents <= 0) throw new IllegalArgumentException("amount must be positive");
this.id = id;
this.amountCents = amountCents;
System.out.println("constructor ran for " + id);
}
public static Payment of(String id, long amountCents) { // static factory
return new Payment(id, amountCents);
}
@Override
public Payment clone() {
try {
return (Payment) super.clone(); // Object.clone(): field copy
} catch (CloneNotSupportedException e) {
throw new AssertionError(e); // cannot happen, we are Cloneable
}
}
}
The everyday paths: new, factories and builders
Try it yourself first
Before reading on, count how many times constructor ran is printed.
Payment a = new Payment("p-1", 500); // prints: constructor ran for p-1
Payment b = Payment.of("p-2", 700); // prints: constructor ran for p-2
Payment c = a.clone(); // prints nothing
System.out.println(c != a); // prints: true (a separate object)
Two lines print. Notice:
Payment.ofis a plain method that callsnew. The value of a static factory is in the API: it has a name, it can return a cached instance (Integer.valueOf(100),List.of()), it can return a subtype, and it can run setup after the object is fully built.clone()printed nothing.Object.clone()is a native method that allocates a new object of the same runtime class and copies every field bit for bit. No constructor runs, at any level of the hierarchy.clone()needsCloneable. Without the marker interface,Object.clone()throwsCloneNotSupportedException. It is alsoprotected, which is why I override it aspublicwith a covariant return type.
A builder is the same idea as a factory, for classes with many optional parameters. Instead of a constructor with eight arguments where two longs are easy to swap, you set named values and call build(), which calls the real constructor once.
Refund refund = Refund.builder()
.paymentId("p-1")
.amountCents(200)
.reason("damaged")
.build(); // build() calls new Refund(this) and validates once
The builder itself is an extra short-lived object. In a request handler that's irrelevant; in a loop doing ten million iterations, it's one more thing for escape analysis to remove (more on that below).
Reflection and method handles
Frameworks such as Spring, Jackson and JPA don't know your classes at compile time, so they create instances from a Class object. The modern reflective call is getDeclaredConstructor(...).newInstance(...).
Constructor<Payment> ctor = Payment.class.getDeclaredConstructor(String.class, long.class);
Payment r = ctor.newInstance("p-3", 900); // prints: constructor ran for p-3
try {
ctor.newInstance("p-4", -1);
} catch (InvocationTargetException e) {
System.out.println(e.getCause()); // prints: java.lang.IllegalArgumentException: amount must be positive
}
MethodHandle mh = MethodHandles.lookup()
.findConstructor(Payment.class, MethodType.methodType(void.class, String.class, long.class));
Payment m = (Payment) mh.invoke("p-5", 1_000L); // prints: constructor ran for p-5
Notice:
- The constructor still runs, including its validation. Exceptions come back wrapped in
InvocationTargetException, which trips people up when they log only the outer exception. Class.newInstance()is deprecated since Java 9. It could rethrow checked exceptions the caller never declared. UsegetDeclaredConstructor().newInstance().- Private constructors need
setAccessible(true), and since Java 16 strong encapsulation of JDK internals means that call fails withInaccessibleObjectExceptionfor packages a module doesn't open. - A
MethodHandleis checked once, at lookup. After that the JIT can inline it like a normal call, which is why newer frameworks prefer it in hot paths.
Deserialization
Java serialization rebuilds an object graph from a byte stream. For a Serializable class, ObjectInputStream allocates the instance, runs the no-arg constructor of the first non-serializable superclass (for Payment, that's Object()), then writes the field values from the stream straight into the object. Payment(String, long) never runs, so a crafted or old stream can produce a Payment with amountCents = -500.
There are two well-known exceptions. Externalizable classes must have a public no-arg constructor, which is called before readExternal. Records (Java 16+) are deserialized through their canonical constructor, so their validation does run. That alone is a good reason to model wire data as records. JSON libraries are a different mechanism: Jackson calls a constructor or factory you annotate (or a no-arg constructor followed by setters), so what runs depends on how you configured it.
03What happens when new runs, step by step
Try it yourself first
Before reading on, list the steps between new Payment("p-1", 500) and the reference being stored in a. Try for five.
At the bytecode level the expression is new, dup, push the arguments, invokespecial Payment.<init>, then astore. The dup exists because invokespecial consumes one copy of the reference as the constructor's this, and the caller needs a second copy to store. Inside HotSpot, that turns into six steps, shown below.
Premium lesson
Keep reading with the Java Pass
The rest of this lesson is part of Java One-Stop Interview Prep: in-depth explanations, diagrams, model answers and how to say them in the interview.
- Real world: the constructor that wasn't called
- Advanced: escape analysis, Unsafe and the edge cases
- How to say it in the interview
From ₹199 · one-time payment · No auto-renewal
Further reading (9)
- JLS §12.5 Creation of New Class Instances
- JLS §15.9 Class Instance Creation Expressions
- JVMS §6.5 new (instruction)
- Java Object Serialization Specification §3 Object Input Classes
- Javadoc: java.lang.reflect.Constructor
- Javadoc: java.lang.invoke.MethodHandles.Lookup
- Javadoc: Object.clone()
- Effective Java, 3rd ed., Items 1, 2, 6 and 13 (static factories, builders, avoiding unnecessary objects, clone)
- Baeldung: A Guide to Creating Objects in Java