Java super and this Keywords (Methods & Fields) — Syntax, Uses, Examples & Best Practices
Everything you need to know about Java super and this keywords in the context of methods and fields — this reference disambiguation, super method calls, hidden field access, method chaining, static context rules, differences, anti-patterns, and real-world production code examples.
Last Updated
March 2026
Read Time
20 min
Level
Beginner
Chapter
20 of 35
What are this and super Keywords in Java?
In Java, this and super are two special reference keywords that are available inside instance methods and constructors. They provide a way to explicitly refer to specific objects and class members when there is ambiguity or when you need to reach beyond the current class.
this is a reference to the current object — the specific instance on which the method is being called. It resolves ambiguity between instance fields and local variables, enables passing the current object as an argument, and powers fluent method chaining. super is a reference to the parent class of the current object's class. It allows a subclass to explicitly call an overridden parent method or access a field hidden by a same-named subclass field.
this Keyword — All Uses in Methods and Fields
The this keyword has four distinct uses in the context of methods and fields (excluding the constructor use this() which was covered separately). Understanding each use clearly prevents a whole class of subtle bugs.
When a method parameter or local variable has the same name as an instance field, 'this.field' explicitly refers to the instance field. Without 'this', the local variable shadows the field. Most common in constructors and setters: this.name = name. Required when names clash; optional (but readable) when no clash exists.
this.method() explicitly calls another instance method on the current object. The 'this.' prefix is optional when calling instance methods from within the same class — Java resolves it automatically. However, writing it explicitly makes the intent clear: 'I am calling a method of this same object.' Useful for documentation and clarity in complex classes.
The current object can be passed as an argument to another method or constructor using 'this'. Example: eventSystem.register(this) — registers the current object as a listener. Or: someHelper.process(this) — passes current object to a helper for processing. This pattern is common in observer, listener, and callback designs.
Returning 'this' from a method allows the caller to chain multiple method calls on the same object in one expression: obj.setName('A').setAge(25).setEmail('a@b.com'). This is the foundation of the Builder pattern and Fluent API design. Each method does its work on the object and returns the same object reference, enabling the next call.
this for Field Access — Resolving Shadowing
The most common use of this is resolving variable shadowing — when a method parameter or local variable has the same name as an instance field. Without this, the local variable takes precedence (shadows the field). this.fieldName always explicitly refers to the instance field, regardless of any local variable with the same name.
public class Employee {
// Instance fields
private String name;
private int age;
private double salary;
private String department;
// ─── Constructor — 'this' required to distinguish field from parameter ──
public Employee(String name, int age, double salary, String department) {
this.name = name; // field = parameter
this.age = age;
this.salary = salary;
this.department = department;
}
// ─── Setter — 'this' required ─────────────────────────────────────────
public void setName(String name) {
this.name = name; // ✅ 'this.name' = field; 'name' = parameter
}
// ─── Method with local var — 'this' distinguishes field ───────────────
public void applyRaise(double salary) {
// 'salary' here is the PARAMETER (raise amount), not the field
double previousSalary = this.salary; // 'this.salary' = field
this.salary = this.salary + salary; // field += parameter
System.out.printf("Salary updated: ₹%.0f → ₹%.0f%n",
previousSalary, this.salary);
}
// ─── When 'this' is optional — no shadowing ──────────────────────────
public void promote(String newDepartment) {
// No local 'department' var — 'this.' optional but can be written for clarity
department = newDepartment; // ✅ works
this.department = newDepartment; // ✅ also works — more explicit
}
// ─── Silent bug WITHOUT 'this' ────────────────────────────────────────
public void setBuggyAge(int age) {
age = age; // ❌ Assigns parameter to itself — field 'this.age' unchanged!
// No compile error — silent bug
}
public void display() {
System.out.println(name + " | " + department + " | ₹" + salary);
}
public static void main(String[] args) {
Employee e = new Employee("Meera", 28, 60000, "Engineering");
e.display(); // Meera | Engineering | ₹60000.0
e.applyRaise(10000); // Salary updated: ₹60000 → ₹70000
e.display(); // Meera | Engineering | ₹70000.0
e.setBuggyAge(35); // Bug: age field still 28
// e.age would show 28, not 35 — silent assignment-to-self bug
}
}this for Method Calls — Calling Current Object's Methods
Inside an instance method, you can call other instance methods of the same object with or without the this. prefix. Java resolves unqualified method calls to the current object automatically. Writing this.methodName() explicitly makes the code more readable in complex classes — it clearly documents that the method belongs to the current object, not a local helper or static utility.
public class Order {
private String orderId;
private double subtotal;
private double discountAmount;
private double taxAmount;
private double total;
private boolean isProcessed;
public Order(String orderId, double subtotal) {
this.orderId = orderId;
this.subtotal = subtotal;
this.isProcessed = false;
}
// ─── Private helper methods ───────────────────────────────────────────
private double calculateDiscount() {
return subtotal > 1000 ? subtotal * 0.10 : 0;
}
private double calculateTax(double amountAfterDiscount) {
return amountAfterDiscount * 0.18; // 18% GST
}
private void updateTotal() {
// 'this.method()' calls — explicit, self-documenting
this.discountAmount = this.calculateDiscount(); // this. optional
double afterDiscount = subtotal - discountAmount;
this.taxAmount = this.calculateTax(afterDiscount); // this. optional
this.total = afterDiscount + taxAmount;
}
// ─── Public method calls private helpers ──────────────────────────────
public void process() {
if (isProcessed) {
System.out.println("Order " + orderId + " already processed.");
return;
}
updateTotal(); // same as: this.updateTotal();
this.isProcessed = true;
this.printSummary(); // explicit this. — clearly a method of this object
}
public void printSummary() {
System.out.println("Order : " + orderId);
System.out.printf ("Subtotal: ₹%.2f%n", subtotal);
System.out.printf ("Discount: ₹%.2f%n", discountAmount);
System.out.printf ("Tax : ₹%.2f%n", taxAmount);
System.out.printf ("Total : ₹%.2f%n", total);
}
public static void main(String[] args) {
Order o = new Order("ORD-2024-001", 1500.0);
o.process();
// Order : ORD-2024-001
// Subtotal: ₹1500.00
// Discount: ₹150.00
// Tax : ₹243.00
// Total : ₹1593.00
}
}this as Method Argument — Passing Current Object
The this keyword can be passed as an argument to another method or constructor, sending a reference to the current object. This pattern is common in Observer, Listener, Callback, and Delegation designs — where an object needs to register itself with another object or hand itself off for processing.
// Event listener pattern — passing 'this' to register current object
interface ClickListener {
void onClick(String buttonName);
}
class Button {
private String name;
private ClickListener listener;
Button(String name) { this.name = name; }
public void setListener(ClickListener listener) {
this.listener = listener;
}
public void click() {
if (listener != null) listener.onClick(name);
}
}
// Controller implements ClickListener and passes itself
class FormController implements ClickListener {
private Button submitBtn;
private Button cancelBtn;
private String formData;
FormController(String formData) {
this.formData = formData;
this.submitBtn = new Button("Submit");
this.cancelBtn = new Button("Cancel");
// ✅ Passing 'this' — registering current object as the listener
submitBtn.setListener(this); // 'this' = current FormController instance
cancelBtn.setListener(this);
}
@Override
public void onClick(String buttonName) {
if ("Submit".equals(buttonName)) {
System.out.println("Submitting form: " + formData);
} else if ("Cancel".equals(buttonName)) {
System.out.println("Form cancelled.");
}
}
public void simulateUserAction() {
submitBtn.click();
cancelBtn.click();
}
}
// Another pattern: passing 'this' to a helper/utility class
class OrderValidator {
public boolean validate(Order order) {
return order != null && order.getTotal() > 0;
}
}
class Order {
private double total;
Order(double total) { this.total = total; }
public double getTotal() { return total; }
public boolean isValid() {
OrderValidator validator = new OrderValidator();
return validator.validate(this); // ✅ passing 'this' to helper
}
}
public class ThisAsArgument {
public static void main(String[] args) {
FormController form = new FormController("user@example.com");
form.simulateUserAction();
// Output:
// Submitting form: user@example.com
// Form cancelled.
Order o = new Order(1500.0);
System.out.println("Order valid: " + o.isValid()); // true
}
}Returning this — Method Chaining and Fluent APIs
When an instance method returns this, it returns a reference to the current object. The caller can then immediately call another method on the returned object — chaining multiple calls in a single expression. This technique is called method chaining or fluent interface design, and it is the foundation of the Builder pattern, StringBuilder, Stream API, and many modern Java APIs.
// ─── Fluent Builder using 'return this' ──────────────────────────────────
public class EmailMessage {
private String from;
private String to;
private String cc;
private String subject;
private String body;
private boolean isHtml;
private int priority; // 1=high, 2=normal, 3=low
// ─── Each setter returns 'this' — enables chaining ────────────────────
public EmailMessage from(String from) {
this.from = from;
return this; // ← return this = same object
}
public EmailMessage to(String to) {
this.to = to;
return this;
}
public EmailMessage cc(String cc) {
this.cc = cc;
return this;
}
public EmailMessage subject(String subject) {
this.subject = subject;
return this;
}
public EmailMessage body(String body) {
this.body = body;
this.isHtml = false;
return this;
}
public EmailMessage htmlBody(String body) {
this.body = body;
this.isHtml = true;
return this;
}
public EmailMessage highPriority() {
this.priority = 1;
return this;
}
public void send() {
System.out.println("Sending email:");
System.out.println(" From : " + from);
System.out.println(" To : " + to);
if (cc != null) System.out.println(" CC : " + cc);
System.out.println(" Subject : " + subject);
System.out.println(" Priority: " + (priority == 1 ? "HIGH" : "Normal"));
System.out.println(" Body : " + body);
}
}
public class MethodChaining {
public static void main(String[] args) {
// ❌ Without chaining — verbose, repetitive variable
EmailMessage msg1 = new EmailMessage();
msg1.from("admin@company.com");
msg1.to("user@example.com");
msg1.subject("Welcome");
msg1.body("Welcome to our platform!");
msg1.send();
System.out.println("---");
// ✅ With chaining — clean, readable, expressive
new EmailMessage()
.from("no-reply@company.com")
.to("priya@example.com")
.cc("manager@company.com")
.subject("Order Confirmed — #ORD-2024-881")
.htmlBody("<h1>Your order is confirmed!</h1>")
.highPriority()
.send();
// Output:
// Sending email:
// From : no-reply@company.com
// To : priya@example.com
// CC : manager@company.com
// Subject : Order Confirmed — #ORD-2024-881
// Priority: HIGH
// Body : <h1>Your order is confirmed!</h1>
}
}super Keyword — All Uses in Methods and Fields
In the context of methods and fields (excluding the constructor use super()), the super keyword has two distinct uses: calling an overridden parent method, and accessing a hidden parent field. Both are used exclusively in subclass instance methods.
When a subclass overrides a method, the parent's version is no longer called automatically. super.methodName() explicitly invokes the parent class's version of that overridden method from within the overriding method. Use case: the subclass wants to EXTEND (add to) the parent's behaviour rather than completely REPLACE it. The parent's logic runs first, then the subclass adds its own logic on top.
When a subclass declares a field with the same name as a parent class field, the subclass field HIDES the parent field — both exist in memory, but the unqualified name refers to the subclass field. super.fieldName explicitly accesses the parent's version of the field. Note: field hiding is rarely used and generally considered poor practice. Method overriding is almost always preferred over field hiding.
super for Method Calls — Invoking Overridden Parent Methods
super.methodName() calls the parent class's version of a method that the current class has overridden. This is used when the subclass wants to augment the parent's behaviour — adding extra logic before or after the parent's implementation — rather than replacing it entirely. Without super.method(), the parent's implementation is simply skipped.
class Logger {
public void log(String message) {
System.out.println("[LOG] " + message);
}
public String format(String text) {
return text.trim();
}
}
class TimestampLogger extends Logger {
// ✅ Extends parent behaviour — calls super.log() then adds timestamp
@Override
public void log(String message) {
super.log(message); // ← parent's log() runs first
System.out.println(" [at: " + java.time.LocalTime.now() + "]");
}
// ✅ Extends parent format — adds uppercase on top of trim
@Override
public String format(String text) {
String trimmed = super.format(text); // parent trims first
return trimmed.toUpperCase(); // child adds uppercase
}
}
class AuditLogger extends TimestampLogger {
private String auditUser;
AuditLogger(String user) { this.auditUser = user; }
// ✅ Three-level chain: AuditLogger → TimestampLogger → Logger
@Override
public void log(String message) {
super.log(message); // calls TimestampLogger.log() which calls Logger.log()
System.out.println(" [audit by: " + auditUser + "]");
}
}
// Real-world pattern: Template Method with super
class Animal {
public String describe() {
return "I am an animal";
}
}
class Dog extends Animal {
private String breed;
Dog(String breed) { this.breed = breed; }
@Override
public String describe() {
// ✅ Get parent description and extend it
return super.describe() + ", specifically a " + breed + " dog";
}
}
class GuideDog extends Dog {
GuideDog(String breed) { super(breed); }
@Override
public String describe() {
return super.describe() + ", trained as a guide dog";
// Result: super.describe() calls Dog.describe()
// which calls super.describe() = Animal.describe()
// Final: 'I am an animal, specifically a Labrador dog, trained as a guide dog'
}
}
public class SuperMethodCall {
public static void main(String[] args) {
TimestampLogger tl = new TimestampLogger();
tl.log("Server started");
// Output:
// [LOG] Server started
// [at: 10:45:23.123456]
System.out.println(tl.format(" hello world "));
// Output: HELLO WORLD
AuditLogger al = new AuditLogger("admin");
al.log("Payment processed");
// Output:
// [LOG] Payment processed
// [at: 10:45:23.124001]
// [audit by: admin]
GuideDog gd = new GuideDog("Labrador");
System.out.println(gd.describe());
// Output: I am an animal, specifically a Labrador dog, trained as a guide dog
}
}super for Field Access — Accessing Hidden Parent Fields
When a subclass declares a field with the same name as a field in the parent class, the subclass field hides the parent field (not overrides — only methods are overridden, fields are hidden). Both fields exist in memory simultaneously — the parent's field as part of the inherited state, and the subclass's field as the new declaration. super.fieldName provides access to the parent's hidden field from within the subclass.
class Shape {
// Parent field
String color = "White";
int sides = 0;
}
class ColoredShape extends Shape {
// ⚠️ Hides parent's 'color' field — both exist in memory
String color = "Red"; // Subclass field — hides Shape.color
public void showColors() {
System.out.println("Subclass color : " + color); // Red
System.out.println("this.color : " + this.color); // Red (same)
System.out.println("super.color : " + super.color); // White (parent's)
}
public void showSides() {
// 'sides' not hidden — only one 'sides' field (inherited from Shape)
System.out.println("sides : " + sides); // 0 (inherited)
System.out.println("super.sides: " + super.sides); // 0 (same field)
System.out.println("this.sides : " + this.sides); // 0 (same field)
}
}
// ─── Field hiding vs Method overriding — CRITICAL DIFFERENCE ─────────────
class Base {
String type = "Base field";
public String getType() { return "Base method"; }
}
class Child extends Base {
String type = "Child field"; // HIDES parent field
@Override
public String getType() { return "Child method"; } // OVERRIDES parent method
}
public class SuperFieldAccess {
public static void main(String[] args) {
ColoredShape cs = new ColoredShape();
cs.showColors();
// Output:
// Subclass color : Red
// this.color : Red
// super.color : White
System.out.println("--- Field hiding vs Method overriding ---");
Base ref = new Child(); // Parent reference, child object
// FIELD ACCESS — resolved by REFERENCE TYPE (compile-time)
System.out.println(ref.type); // Base field ← reference is Base
// METHOD CALL — resolved by OBJECT TYPE (runtime — polymorphism)
System.out.println(ref.getType()); // Child method ← object is Child
// Key insight: fields are HIDDEN (static binding), methods are OVERRIDDEN (dynamic)
Child childRef = new Child();
System.out.println(childRef.type); // Child field
System.out.println(childRef.getType()); // Child method
}
}super vs this — Key Differences
While both this and super are reference keywords available in instance context, they refer to fundamentally different targets and serve different purposes.
class Vehicle {
String type = "Vehicle";
int speed = 60;
public void describe() {
System.out.println("Vehicle: type=" + type + ", speed=" + speed);
}
}
class Car extends Vehicle {
String type = "Car"; // Hides Vehicle.type
int doors = 4;
public void showAll() {
// 'this' — current object's fields
System.out.println("this.type : " + this.type); // Car
System.out.println("this.doors : " + this.doors); // 4
System.out.println("this.speed : " + this.speed); // 60 (inherited)
// 'super' — parent's field
System.out.println("super.type : " + super.type); // Vehicle
System.out.println("super.speed: " + super.speed); // 60 (same — not hidden)
}
@Override
public void describe() {
super.describe(); // ← call parent's version first
// then add car-specific info
System.out.println("Car : type=" + this.type + ", doors=" + this.doors);
}
// Method chaining with 'this'
public Car setDoors(int doors) {
this.doors = doors;
return this; // ← return this for chaining
}
public Car setSpeed(int speed) {
this.speed = speed;
return this;
}
}
public class SuperVsThis {
public static void main(String[] args) {
Car c = new Car();
c.showAll();
// this.type : Car
// this.doors : 4
// this.speed : 60
// super.type : Vehicle
// super.speed: 60
System.out.println("---");
c.describe();
// Vehicle: type=Vehicle, speed=60
// Car : type=Car, doors=4
System.out.println("---");
// Method chaining with 'this'
c.setDoors(2).setSpeed(120);
c.describe();
// Vehicle: type=Vehicle, speed=120
// Car : type=Car, doors=2
}
}this and super in Static Context — Why They Don't Work
Neither this nor super can be used inside a static method or a static initializer block. The reason is fundamental: static methods belong to the class, not to any specific object. When a static method executes, there is no current object — there is no this. And without a current object, there is no basis for super either.
class Parent {
static int staticCount = 0;
int instanceId;
Parent() {
staticCount++;
this.instanceId = staticCount; // ✅ 'this' valid in constructor
}
static void staticMethod() {
System.out.println("Parent static: " + staticCount);
// this.instanceId // ❌ COMPILE ERROR: cannot use this in static context
// super.something // ❌ COMPILE ERROR: cannot use super in static context
}
void instanceMethod() {
System.out.println("Instance id: " + this.instanceId); // ✅ valid
}
}
class Child extends Parent {
String name;
Child(String name) {
super(); // ✅ super() in constructor — valid
this.name = name; // ✅ this in constructor — valid
}
@Override
void instanceMethod() {
super.instanceMethod(); // ✅ super.method() in instance method — valid
System.out.println("Child name: " + this.name); // ✅ this in instance method
}
static void childStatic() {
// System.out.println(this.name); // ❌ COMPILE ERROR
// System.out.println(super.staticCount); // ❌ COMPILE ERROR
System.out.println(Parent.staticCount); // ✅ Use class name for static
}
}
public class StaticContext {
public static void main(String[] args) {
// Static method — no object needed
Parent.staticMethod(); // Output: Parent static: 0
Child c1 = new Child("Arjun");
Child c2 = new Child("Divya");
c1.instanceMethod();
// Instance id: 1
// Child name: Arjun
Child.childStatic(); // Output: Parent static: 2
}
}Common Mistakes & Pitfalls
These mistakes involving this and super are consistently found in Java beginner code. Most produce subtle runtime bugs rather than compile errors — making them especially dangerous.
class Animal {
String name = "Animal";
public void sound() { System.out.println("Generic sound"); }
}
class Dog extends Animal {
String name = "Dog"; // hides Animal.name
// ❌ MISTAKE 1: Missing 'this' in setter — silent field-not-set bug
String breed;
public void setBreed(String breed) {
breed = breed; // ❌ param assigned to itself — field unchanged
}
// ✅ Fix: this.breed = breed;
// ❌ MISTAKE 2: Using 'this' in static method
// public static void staticDemo() {
// System.out.println(this.name); // ❌ COMPILE ERROR
// }
// ❌ MISTAKE 3: Expecting super to skip to grandparent
// If Dog extends Mammal extends Animal,
// super.sound() calls Mammal.sound() NOT Animal.sound() directly
// ❌ MISTAKE 4: Forgetting super.method() — accidentally replacing, not extending
@Override
public void sound() {
// Missing super.sound() — parent's logic is completely lost
System.out.println("Woof!");
// If parent had important side effects (logging, event firing), they are GONE
}
// ✅ Fix: call super.sound() when parent behaviour should be preserved
public void soundExtended() {
super.sound(); // Parent logic runs first
System.out.println("Woof!"); // Then child adds its own
}
// ❌ MISTAKE 5: Confusing field hiding with method overriding
public void showName() {
System.out.println(name); // Dog (subclass field)
System.out.println(this.name); // Dog (same)
System.out.println(super.name); // Animal (parent's hidden field)
// BUT via parent reference: parentRef.name = Animal (compile-time resolution)
}
// ❌ MISTAKE 6: Returning 'this' from void method accidentally — compile error
// public void setBreedVoid(String breed) {
// this.breed = breed;
// return this; // ❌ COMPILE ERROR: void method cannot return value
// }
// ✅ Fix: change return type to Dog (or Animal) to enable chaining
public Dog setBreedFixed(String breed) {
this.breed = breed;
return this; // ✅ Now valid — method returns Dog
}
}Bad Practices & Anti-Patterns
These anti-patterns involving this and super appear in code reviews and lead to maintenance issues, subtle bugs, or code that is difficult to understand.
Declaring a field in a subclass with the same name as a parent field creates a hidden field situation — both exist, accessing either requires care, and the behaviour when accessed via a parent reference is counter-intuitive (parent's field is used). Avoid field hiding entirely — it is almost never intentional design. Use different field names in subclasses, or consolidate the field in the parent with protected access. Method overriding is a feature; field hiding is almost always an accident.
In frameworks like Android (onCreate, onResume), Spring, or JUnit (setUp, tearDown), overriding lifecycle methods without calling super.method() breaks the framework's internal initialization. Rule: if you override a method from a framework base class, check whether the parent's implementation does something important. When in doubt, call super.method(). The @CallSuper annotation (in Android) documents methods where super must be called.
Writing 'this.' before every single field access and method call — even when there is no shadowing — makes code unnecessarily verbose. this.name when there is no local 'name' variable, and this.process() when calling a private method, adds noise without clarity. Use this. when it adds value: in constructors/setters (disambiguates parameter), when returning this, or when passing this as an argument. Elsewhere, let Java's automatic resolution work.
While returning this enables fluent APIs, chaining 10+ method calls on a mutable object makes error handling and debugging very difficult — if one method throws an exception mid-chain, it's hard to identify which step failed. For complex construction with many optional parameters, prefer the Builder pattern (separate builder object) over chaining methods directly on the target object. Builders are easier to validate, test, and reason about.
Sometimes a developer uses super.method() in an inner method to 'skip' validation that the overriding method adds. Example: a child class adds validation in processPayment(), but internally calls super.processPayment() to bypass it. This breaks encapsulation and the class's contract. If internal bypass is needed, refactor the parent to have a separate protected doProcess() (template method pattern) that subclasses can call without bypassing validation.
Declaring public (non-final) fields in classes designed for inheritance causes field hiding problems as soon as a subclass declares a field with the same name. Since field access is resolved by reference type (not object type), behaviour becomes unpredictable when using polymorphism. Always make fields private in inheritable classes and expose them via protected or public getter methods. Methods participate in polymorphism; fields do not.
Real-World Production Code Examples
The following examples demonstrate idiomatic this and super usage in real enterprise Java patterns — domain model builders, layered service classes, and framework-style lifecycle methods.
package com.techsustainify.db.query;
import java.util.ArrayList;
import java.util.List;
// Fluent SQL query builder — heavy use of 'return this'
public class QueryBuilder {
private String table;
private List<String> columns = new ArrayList<>();
private List<String> conditions = new ArrayList<>();
private List<String> orderBy = new ArrayList<>();
private Integer limit;
private Integer offset;
public QueryBuilder from(String table) {
this.table = table;
return this;
}
public QueryBuilder select(String... cols) {
for (String col : cols) this.columns.add(col);
return this;
}
public QueryBuilder where(String condition) {
this.conditions.add(condition);
return this;
}
public QueryBuilder orderBy(String column, String direction) {
this.orderBy.add(column + " " + direction);
return this;
}
public QueryBuilder limit(int limit) {
this.limit = limit;
return this;
}
public QueryBuilder offset(int offset) {
this.offset = offset;
return this;
}
public String build() {
StringBuilder sql = new StringBuilder("SELECT ");
sql.append(columns.isEmpty() ? "*" : String.join(", ", columns));
sql.append(" FROM ").append(table);
if (!conditions.isEmpty())
sql.append(" WHERE ").append(String.join(" AND ", conditions));
if (!orderBy.isEmpty())
sql.append(" ORDER BY ").append(String.join(", ", orderBy));
if (limit != null) sql.append(" LIMIT ").append(limit);
if (offset != null) sql.append(" OFFSET ").append(offset);
return sql.toString();
}
}
class QueryBuilderDemo {
public static void main(String[] args) {
String query = new QueryBuilder()
.from("orders")
.select("order_id", "customer_name", "total_amount")
.where("status = 'PENDING'")
.where("total_amount > 500")
.orderBy("created_at", "DESC")
.limit(20)
.offset(40)
.build();
System.out.println(query);
// SELECT order_id, customer_name, total_amount FROM orders
// WHERE status = 'PENDING' AND total_amount > 500
// ORDER BY created_at DESC LIMIT 20 OFFSET 40
}
}package com.techsustainify.entity;
import java.time.LocalDateTime;
// Base entity — all entities extend this
public abstract class AuditableEntity {
private Long id;
private LocalDateTime createdAt;
private LocalDateTime updatedAt;
private String createdBy;
private String updatedBy;
// Called before INSERT — subclasses MUST call super.onPrePersist()
public void onPrePersist(String actor) {
this.createdAt = LocalDateTime.now();
this.updatedAt = this.createdAt;
this.createdBy = actor;
this.updatedBy = actor;
System.out.println("[AuditableEntity] onPrePersist: " + actor);
}
// Called before UPDATE — subclasses MUST call super.onPreUpdate()
public void onPreUpdate(String actor) {
this.updatedAt = LocalDateTime.now();
this.updatedBy = actor;
System.out.println("[AuditableEntity] onPreUpdate: " + actor);
}
public Long getId() { return id; }
public LocalDateTime getCreatedAt() { return createdAt; }
public LocalDateTime getUpdatedAt() { return updatedAt; }
public String getCreatedBy() { return createdBy; }
}
public class UserEntity extends AuditableEntity {
private String username;
private String email;
private String role;
public UserEntity(String username, String email, String role) {
this.username = username;
this.email = email;
this.role = role;
}
@Override
public void onPrePersist(String actor) {
super.onPrePersist(actor); // ✅ Parent's audit fields set first
// Then add entity-specific pre-persist logic
System.out.println("[UserEntity] Setting default role if absent");
if (this.role == null) this.role = "USER";
}
@Override
public void onPreUpdate(String actor) {
super.onPreUpdate(actor); // ✅ Parent updates updatedAt/updatedBy
System.out.println("[UserEntity] Logging user update event");
}
@Override
public String toString() {
return String.format("User[%s | %s | %s | created=%s]",
username, email, role, getCreatedAt());
}
}
class LifecycleDemo {
public static void main(String[] args) {
UserEntity user = new UserEntity("ravi_k", "ravi@company.com", null);
user.onPrePersist("system");
// [AuditableEntity] onPrePersist: system
// [UserEntity] Setting default role if absent
System.out.println(user);
// User[ravi_k | ravi@company.com | USER | created=2026-03-20T10:45:23]
user.onPreUpdate("admin");
// [AuditableEntity] onPreUpdate: admin
// [UserEntity] Logging user update event
}
}this and super Resolution Flowchart
This flowchart shows how the compiler resolves this and super references when encountered in Java source code.
Code Execution Flow — from source to output
Java super and this Keywords Interview Questions — Beginner to Advanced
These questions are consistently asked in Java fresher and mid-level interviews, OCPJP certification exams, and campus placement tests covering Java OOP concepts.
Practice Questions — Test Your this and super Knowledge
Attempt each question independently before reading the answer — active recall significantly improves long-term retention compared to passive reading.
1. What is the output? class A { int x = 5; void show() { System.out.println("A: " + x); } } class B extends A { int x = 10; @Override void show() { System.out.println("B: " + x); System.out.println("super: " + super.x); super.show(); } } public class Test { public static void main(String[] args) { new B().show(); } }
Easy2. Find the bug: class Rectangle { private double width; private double height; public Rectangle(double width, double height) { width = width; height = height; } public double area() { return width * height; } } // new Rectangle(5, 3).area() returns?
Easy3. Will this compile? What is the output? class Animal { public void eat() { System.out.println("Animal eating"); } } class Dog extends Animal { @Override public void eat() { super.eat(); System.out.println("Dog eating"); } public void action() { this.eat(); } } public class Test { public static void main(String[] args) { new Dog().action(); } }
Easy4. Rewrite using method chaining: StringBuilder sb = new StringBuilder(); sb.append("Hello"); sb.append(", "); sb.append("World"); sb.append("!"); String result = sb.toString(); System.out.println(result);
Easy5. What is the output? Explain why. class Parent { String name = "Parent"; void display() { System.out.println(name); } } class Child extends Parent { String name = "Child"; @Override void display() { System.out.println(name); } } public class Test { public static void main(String[] args) { Parent p = new Child(); System.out.println(p.name); p.display(); } }
Medium6. Design a fluent Person class where you can chain: new Person().name("Arjun").age(25).city("Mumbai") and then call print() to display all fields.
Medium7. What is the output? Why is the field access surprising? class Base { int value = 100; int getValue() { return value; } } class Derived extends Base { int value = 200; @Override int getValue() { return value; } } public class Test { public static void main(String[] args) { Base obj = new Derived(); System.out.println(obj.value); // Line 1 System.out.println(obj.getValue()); // Line 2 } }
Hard8. Identify all uses of 'this' and 'super' in this code and explain each: class Vehicle { protected String brand; protected int speed; Vehicle(String brand, int speed) { this.brand = brand; this.speed = speed; } public String info() { return brand + "@" + speed; } } class ElectricCar extends Vehicle { private int batteryKWh; ElectricCar(String brand, int speed, int batteryKWh) { super(brand, speed); this.batteryKWh = batteryKWh; } @Override public String info() { return super.info() + "+" + batteryKWh + "kWh"; } public ElectricCar setBattery(int kWh) { this.batteryKWh = kWh; return this; } }
HardConclusion — this and super: The Two Lenses of Java OOP
this and super are two lenses through which Java code looks at its own object hierarchy. this looks inward — at the current object, its own fields and methods. super looks upward — at the parent class, its implementation before it was overridden, its fields before they were hidden. Together they give subclasses full visibility into both their own state and their inherited state.
Mastering these keywords separates clean OOP code from buggy, confused code. The hallmarks: this.field = param in every constructor and setter, super.method() whenever extending (not replacing) parent behaviour, return this for fluent APIs, never using either in static contexts, and always preferring method overriding over field hiding.
Your next step: Java Inheritance — where you will see how super fits into the full inheritance model and how the combination of constructor chaining, method overriding, and field access patterns you have now mastered enables powerful class hierarchy designs. ☕