Topic 56 of 119 6. Object-Oriented Programming in Java Interactive Tutorial

Java Default Constructor: Automatic Creation and Behavior

14 mins read Last updated: 22 Aug 2026 26,390 views

Direct Answer & Core Concept

A default constructor is one the compiler writes for you rather than one you write. It is generated only when a class declares no constructor of any kind, and it is equivalent to writing this yourself: That body is the whole of it. No field assignment, no validation, nothing. It calls the parent no-argument constructor and returns, leaving every field at the default value for its type: 0 for primitives, false for boolean, and null for every reference type. Three facts explain almost every surprise involving it. First, declaring any constructor, including a no-argument one, suppresses generation. Second, the generated constructor takes the same access modifier as the class, so a package-private class does not gain a public constructor. Third, it interacts badly with final fields, since there is no legal way to assign them. That third point is why the rule is not merely academic. Add a final field to a class with no constructor and the build fails, because the generated body cannot assign it.

Detailed Explanation & Mechanics

1) The One Condition for Generation

The compiler generates a default constructor if and only if the class body declares no constructor. Not "no parameterized constructor", and not "no public constructor". None at all.

Main.java
Run Online
// This class HAS a default constructor, generated silently
public class Point {
    private int x;
    private int y;
}// This class does NOT, because one constructor is declared
public class SizedPoint {
    private int size;
    public SizedPoint(int size) {
        this.size = size;
    }
}


Diagram showing two classes, one receiving a compiler-generated default constructor and one losing it
Generation depends on one thing only: whether any constructor appears in the source.

2) What the Generated Body Contains

Exactly one statement, the implicit call to the parent no-argument constructor.

Main.java
Run Online
// What javac synthesises for Point above
public Point() {
    super();
}

No field initialisers appear in the generated constructor. They are handled by the field-initialisation phase that runs before any constructor body, as covered in the constructors topic.

What the default constructor doesWhat it does not do
Calls super()Assign any field
Leaves fields at type defaultsRun any validation
Is public if the class is publicBe inherited by subclasses
Exists only in the bytecodeAppear in your source file

3) The Default Values It Leaves Behind

Because the body assigns nothing, every field keeps the zero value for its type. This is the single most visible behaviour and the source of a great many null-related bugs.

Main.java
Run Online
public class Settings {
    int timeoutSeconds;
    // 0
    boolean enabled;
    // false
    String endpoint;
    // null
    double ratio;
    // 0.0
    int[] retries;
    // null, not an empty array
    char initial;
    // the null character
}

A boolean enabled defaults to false and a String endpoint defaults to null. A settings object built with the default constructor is therefore not usable, and nothing warns you. This is the practical reason to prefer a constructor that assigns meaningful values over relying on a generated one.

4) Suppression: Any Constructor Removes It

This is the rule that causes the most damage, because the break surfaces far from the change.

Main.java
Run Online
// Stage one: Session has a default constructor, so this compiles
public class Session {
    private final String userId;
    public String getUserId() {
        return userId;
    }
}Session guest = new Session();
// fine, and the object has userId == null
Main.java
Run Online
// Stage two: one constructor is added, and nothing else in the file changes
public class Session {
    private final String userId;
    public Session(String userId) {
        this.userId = userId;
    } public String getUserId() {
        return userId;
    }
}Session guest = new Session();
// COMPILE ERROR

The constructor that disappeared was never in the source, so searching for it finds nothing. The error message points at a call site that has not been edited.

One important refinement: adding an explicit no-argument constructor does not remove any functionality, since the same signature is still available. What it changes is the access level and the body, which is exactly how you fix a default constructor that is too permissive.

5) Access Modifier Rules

The generated constructor takes the same access modifier as the class. This is the detail most people get wrong when they assume new MyClass() always works from anywhere.

Class declarationGenerated constructor
public class Apublic A()
class BB(), package-private
public final class Cpublic C()

So a package-private class cannot be constructed from another package until you write a public constructor explicitly. Adding an explicit constructor is the only way to widen that access.

6) Why Final Fields Break It

A final field must be assigned exactly once, and the compiler checks this for every constructor, including the one it generates. An empty body cannot satisfy that rule.

Main.java
Run Online
// COMPILE ERROR: cannot assign a value to final variable userId
public class Account {
    private final String userId;
    public String getUserId() {
        return userId;
    }
}

The error names the generated constructor even though you never wrote it, which is a confusing message the first time you see it. Declaring any constructor resolves it, because that constructor can perform the assignment.

The same applies to any field that must be initialised. If a field has no in-line initialiser and is not assigned by a constructor, a class with a final field cannot compile without an explicit constructor.

7) Interaction With Inheritance

Default constructors are not inherited, so a subclass must reach its parent through super or rely on the compiler inserting a no-argument call.

Main.java
Run Online
// Parent declares ONLY a parameterized constructor
public class Vehicle {
    private final String model;
    public Vehicle(String model) {
        this.model = model;
    } public String getModel() {
        return model;
    }
}// COMPILE ERROR: no matching super() is available
public class Car extends Vehicle {
}

Add any constructor to Car that calls super(model) and the problem disappears. The constructor chaining topic covers this in full.

8) Records and Enums Are Different

A record always has a canonical constructor, so the default constructor never applies. An enum is never given one either, since an enum cannot be instantiated outside its own constants.

Main.java
Run Online
// A record has a canonical constructor, never a default one
public record Money(long amountInCents) {
    public Money {
        if (amountInCents < ZERO_CENTS) {
            throw new IllegalArgumentException("Amount cannot be negative");
        }
    }
}// An enum gets a private constructor it may not declare
public enum Status {
    ACTIVE, CLOSED
}

For an enum, every constant is created by the runtime through a constructor that behaves like a private one, which is why you cannot write new Status().

9) Common Misconceptions

ClaimVerdict
Every class gets a default constructorFalse. Only classes declaring none.
It is always publicFalse. It matches the class modifier.
It assigns default values to fieldsTrue in effect, but the JVM does that, not it.
It calls super automaticallyTrue. That is its only statement.
Subclasses inherit itFalse. Constructors are never inherited.
Adding a no-arg constructor removes itTrue. The signature survives, the body changes.
You can see it in your source fileFalse. It exists only in the bytecode.
Records and enums get one tooFalse. Both have their own construction rules.

Frequently Asked Questions

When does the compiler generate a default constructor?


Only when a class body declares no constructor of any kind. Declaring a parameterized constructor, a no-argument constructor, or even a private one all suppress generation, since the condition is that no constructor appears in the source rather than that no usable one exists.

What does the generated constructor contain?


A single statement, an implicit call to the parent no-argument constructor. It assigns no fields and performs no validation, so every field keeps the JVM default for its type, which is zero for primitives, false for boolean, and null for reference types.

What is the access modifier of the generated constructor?


The same as the class declaration. A public class receives a public default constructor, while a package-private class receives a package-private one, which means it cannot be constructed from another package until you write an explicit public constructor.

Why does adding a constructor break code elsewhere?


Because it suppresses generation, so a signature that other files relied on disappears without any edit to those files. The compile error appears at a call site that was never modified, and searching the source for the missing constructor finds nothing because it was never written down.

Does adding an explicit no-argument constructor change anything?


The signature stays available, so no call site breaks, but the body and access level become yours to control. That is precisely how you replace a default constructor that is too permissive, or one that leaves required fields at null.

Why can a class with a final field fail to compile?


A final field must be assigned exactly once, and the compiler checks that for every constructor, including the generated one. Since the generated body assigns nothing, the requirement cannot be met. Declaring any constructor, which can perform the assignment, resolves it.

Do subclasses inherit the default constructor?


No, constructors are never inherited. A subclass must invoke a parent constructor through super, or the compiler inserts a call to the parent no-argument constructor. If the parent declares only parameterized constructors, a subclass with no explicit constructor fails to compile.

How can I see whether a default constructor was generated?


Use a decompiler such as javap on the compiled class, which will show a public constructor with an empty body apart from the super call. It never appears in your source, and IDE features such as generated constructor insertion make the behaviour explicit rather than implicit.

Runnable Code Example: Java Default Constructor

Java 17+
Main.java
Run Online
// Shows when the compiler generates a default constructor and what it leaves behind.
public class DefaultConstructorDemo {
    private static final int ZERO = 0;
    private static final String NOT_SET = "NOT_SET";
    // NO CONSTRUCTOR DECLARED, so javac generates one with an empty body.
    // Because this class is public, the generated constructor is public too.
    public static class PlainSettings {
        int timeoutSeconds;
        // left at 0
        boolean enabled;
        // left at false
        String endpoint;
        // left at null
        int[] retries;
        // left at null, NOT an empty array
    } // ONE CONSTRUCTOR IS DECLARED, so nothing is generated.
    public static class SizedSettings {
        private final int size;
        public SizedSettings(int size) {
            if (size < ZERO) {
                throw new IllegalArgumentException("Size cannot be negative");
            } this.size = size;
        } public int getSize() {
            return size;
        }
    } // AN EXPLICIT NO-ARG CONSTRUCTOR KEEPS THE SIGNATURE BUT CHANGES THE BODY.
    // Nothing that already called new SecureConfig() stops compiling.
    public static class SecureConfig {
        private final String endpoint;
        public SecureConfig() {
            // A default constructor would have left endpoint as null
            this.endpoint = NOT_SET;
        } public SecureConfig(String endpoint) {
            if (endpoint == null || endpoint.isBlank()) {
                throw new IllegalArgumentException("Endpoint is required");
            } this.endpoint = endpoint;
        } public String getEndpoint() {
            return endpoint;
        }
    } // A PACKAGE-PRIVATE CLASS GETS A PACKAGE-PRIVATE DEFAULT CONSTRUCTOR
    static class PackagePrivate {
        int value;
    } // A PARENT WITH NO NO-ARG CONSTRUCTOR BREAKS A SUBCLASS THAT DECLARES NONE
    public static class Vehicle {
        private final String model;
        public Vehicle(String model) {
            this.model = model;
        } public String getModel() {
            return model;
        }
    } // So the subclass declares one and calls super explicitly
    public static class Car extends Vehicle {
        private final int doors;
        public Car(String model, int doors) {
            super(model);
            // required, since Vehicle has no no-arg one
            if (doors < ZERO) {
                throw new IllegalArgumentException("Doors cannot be negative");
            } this.doors = doors;
        } public int getDoors() {
            return doors;
        }
    } private static String describe(Object value) {
        if (value == null) {
            return "null";
        } return value.getClass().getSimpleName() + " = " + value;
    } public static void main(String[] args) {
        // GENERATED: this compiles only because no constructor is declared
        PlainSettings generated = new PlainSettings();
        System.out.println("timeoutSeconds: " + generated.timeoutSeconds);
        System.out.println("enabled:        " + generated.enabled);
        System.out.println("endpoint:       " + describe(generated.endpoint));
        System.out.println("retries array:  " + describe(generated.retries));
        // SUPPRESSED: SizedSettings declares a constructor, so new SizedSettings()
        // would not compile at all
        SizedSettings sized = new SizedSettings(64);
        System.out.println("size: " + sized.getSize());
        // THE SIGNATURE SURVIVES WHEN YOU WRITE IT YOURSELF
        SecureConfig safe = new SecureConfig();
        System.out.println("safe endpoint: " + safe.getEndpoint());
        System.out.println("real endpoint: " + new SecureConfig("https://api.example.com").getEndpoint());
        // ACCESS FOLLOWS THE CLASS MODIFIER
        PackagePrivate restricted = new PackagePrivate();
        restricted.value = 7;
        System.out.println("package-private value: " + restricted.value);
        // INHERITANCE STILL REQUIRES AN EXPLICIT super CALL
        Car car = new Car("Model S", 4);
        System.out.println(car.getModel() + " with " + car.getDoors() + " doors");
        try {
            new SecureConfig("");
        } catch (IllegalArgumentException problem) {
            System.out.println("rejected blank endpoint: " + problem.getMessage());
        } /*
            ACTUAL OUTPUT:

                timeoutSeconds: 0
                enabled:        false
                endpoint:       null
                retries array:  null
                size: 64
                safe endpoint: NOT_SET
                real endpoint: https://api.example.com
                package-private value: 7
                Model S with 4 doors
                rejected blank endpoint: Endpoint is required

            THE SEVEN THINGS TO REMEMBER
            1) GENERATION HAPPENS ONLY WHEN NO CONSTRUCTOR APPEARS in the
               source, not when no usable constructor exists. One private
               constructor is enough to suppress it.
            2) THE BODY IS A SINGLE super() CALL. It assigns nothing and
               validates nothing, so every field keeps the JVM default.
            3) DEFAULTS ARE ZERO, FALSE, AND NULL. A reference field is
               null, and an array field is null rather than empty, which
               is the most common surprise.
            4) ACCESS MATCHES THE CLASS. A package-private class gets a
               package-private constructor, so another package cannot call
               new on it until you write a public one.
            5) DECLARING ANY CONSTRUCTOR SUPPRESSES IT. That removes a
               signature other files depend on, and the resulting compile
               error points at code nobody edited.
            6) AN EXPLICIT NO-ARG CONSTRUCTOR IS SAFE TO ADD, because the
               signature survives and you gain control of the body.
            7) FINAL FIELDS REQUIRE AN EXPLICIT CONSTRUCTOR, because the
               generated empty body cannot satisfy the assignment rule.
        */
    }
}

Common Java Technical Interview Questions & Answers

Interview Prep
Q1: When does the compiler not generate a default constructor?\nA1: As soon as any constructor appears in the class body, including a no-argument one and including a private one. The condition is that no constructor is declared in the source, not that no usable constructor exists, so even a constructor nobody can call still suppresses generation.\nQ2: What does the generated default constructor contain?\nA2: A single statement, an implicit call to the parent no-argument constructor. It assigns no fields and runs no validation, which is why every field keeps the JVM default for its type: zero for primitives, false for boolean, and null for reference types including arrays.\nQ3: What access modifier does the generated constructor have?\nA3: The same modifier as the class declaration. A public class receives a public default constructor, while a package-private class receives a package-private one, so it cannot be constructed from another package until an explicit public constructor is written. Adding any constructor is the only way to widen that access.\nQ4: Why does adding a constructor break code in another file?\nA4: Because it suppresses generation, so a signature other files relied on disappears even though nothing in those files changed. The compile error surfaces at a call site that was never edited, and searching the source for the missing constructor finds nothing because the compiler had written it, not you.\nQ5: Is it safe to add an explicit no-argument constructor?\nA5: Yes. The signature stays available so no existing call site breaks, but you gain control of the body and the access level. That is exactly how you replace a default constructor that leaves required fields null or is more permissive than you intend.\nQ6: Why can a class containing a final field fail to compile?\nA6: A final field must be assigned exactly once, and the compiler enforces that for every constructor, including the one it generates. Since the generated body assigns nothing, the requirement cannot be satisfied. Declaring any constructor, which can perform the assignment, resolves the error.\nQ7: Do subclasses inherit the default constructor?\nA7: No, constructors are never inherited. A subclass invokes a parent constructor through super, or the compiler inserts a call to the parent no-argument constructor. If the parent declares only parameterized constructors, a subclass that declares no constructor fails to compile with a missing super() error.\nQ8: How can I confirm whether a default constructor was generated?\nA8: Decompile the compiled class with a tool such as javap. A generated constructor appears as a public or package-private constructor whose body holds nothing but the super call. It never exists in source form, which is why IDE actions that insert constructors explicitly are often preferable for anything beyond a trivial data holder.

Key Takeaways, Best Practices & Clean Code Rules

Java Best Practice & Production Rules

Never rely on a generated constructor for a class that has real state.\nAssign every field explicitly so no object depends on a type default.\nWrite a no-argument constructor when a framework needs to construct then populate.\nDeclare a constructor as soon as a class gains a final field, even if it takes nothing.\nValidate inside the constructor rather than trusting field defaults.\nRemember that declaring any constructor suppresses the generated one.\nAdd an explicit no-argument constructor rather than removing a parameterized one.\nKeep the class public if callers outside the package must construct it.\nPrefer a record over a class relying on default construction.\nDo not treat null defaults as sensible values, especially for arrays and collections.\nAvoid constructors that can leave an object in a useless state, such as with a null endpoint.\nCheck whether an inherited class still compiles after adding a constructor to a parent.\nTreat an unexpected missing no-arg constructor as a signal to add one explicitly.\nVerify constructor visibility matches the package structure you actually want.\nUse javap or a decompiler to confirm what the compiler produced when behaviour is unclear.
We value your privacy & transparency

OperateTools uses cookies and local storage to ensure basic tool functionality, preserve preferences, and analyze aggregated traffic. We never sell your personal data or index your document files. Learn more in our Privacy Policy.