Skip to content

protected, and the Object Behind Every Class

protected is the access level a base class needs to share with its children without opening itself to everyone. And every class you've ever written already extends Object, whether you typed extends or not.

3 min read

Chapter 7 gave you private, which hides a field from everything outside its class — including subclasses. That is often too strict for a base class, whose entire purpose is to be built upon. protected is the middle setting: available to this class and to any subclass, but not to unrelated code.

class BaseTest {
    protected String runId = "RUN-8891";
    private String secret = "internal-token";
    protected String getSecret() {
        return secret;
    }
}
class CartTest extends BaseTest {
    void report() {
        System.out.println("Run id : " + runId);
        System.out.println("Secret : " + getSecret());
    }
}
Run id : RUN-8891
Secret : internal-token

CartTest reads runId directly because it is protected. It cannot touch secret directly at all — that field is private, and the only way in is the protected method the parent chose to expose. That is the parent controlling exactly what it shares, which is the point. A base class with everything public has no boundary; a base class with everything private cannot be extended usefully. protected is the setting that says "for my subclasses."

Every class already extends Object

Here is a fact that explains something from Chapter 7. Write a class with no extends at all, and Java quietly gives it one anyway: every class in Java inherits from a class called Object.

That is why toString and equals existed on your classes before you wrote them — you were not inventing those methods in Chapter 7, you were overriding them. Watch the inherited version:

class Plain {
}
class Nice {
    @Override
    public String toString() {
        return "Nice[a readable object]";
    }
}
Default toString : Plain@b7f23d9
Overridden       : Nice[a readable object]

The first line is Object's own toString: the class name, an @, and a hexadecimal number derived from the object. That number differs on every run, so do not expect to reproduce it exactly — which is itself the lesson, because it is useless in a test report. The second line is what a five-line override buys you. So the inheritance rules you learned in this chapter were already running underneath Chapter 7. Object sits at the root of every hierarchy, supplying toString, equals, and hashCode, and your job is to override the ones that matter for your type.

How deep should an inheritance chain go?

Shallow. Two levels is common and comfortable; four is a warning sign. A framework has BaseTest, then BaseWebTest extends BaseTest, then BaseCheckoutTest extends BaseWebTest, and a new GuestCheckoutTest extends that. Four levels, and nothing in any one of them is duplicated — every level adds something real. But open GuestCheckoutTest and try to say where setUp actually comes from. You cannot, not without opening four files. The IDE will tell you, and the person debugging a flaky run at midnight still has to hold four classes in their head. Most deep chains like this are sharing helper code, not describing what the test genuinely is — and a helper can be a utility object the test holds, rather than a parent it inherits from. Inherit when the child genuinely is a kind of the parent; reach for a helper object when you only want to share code. A deep chain is usually code sharing wearing an inheritance costume, and it costs the next reader more than it saved you.

Inheritance is the backbone of a framework's structure precisely because of what this chapter covered. BaseTest holds the driver, the config, and the setup and teardown that every test needs, so a test class carries only its own checks. BasePage holds the driver reference and shared element helpers, and each Page Object extends it. A BaseApiTest sets up the REST client the same way. The @Override habit matters here more than anywhere: an overridden setup that silently does not override is a test running with the wrong preconditions, and that produces failures nobody can reproduce.

Everything in this chapter has been about one class built on another. There is one more relationship left in Java's toolkit, and it looks similar on the surface but works completely differently — it's what makes WebDriver not a class at all.

This article is part of Automation Foundations, in the Automation Engineering track. See the full sequence to find what comes next.