Why does Java's Stack implementation break substitutability principles and encapsulation best practices

I’m confused about this concept and need help understanding it better. I think it’s related to how Stack extends Vector in Java but I’m not sure exactly what the problem is.

My understanding is that it breaks substitutability because a stack shouldn’t really behave like a vector. When you need vector functionality you can’t just swap in a stack and expect it to work the same way. But I’m not totally clear on this.

For the encapsulation part I think it has something to do with exposing internal implementation details but I’m just guessing. If you hide the implementation behind private methods then users won’t know it’s built on top of a vector internally.

Can someone break this down for me and explain what exactly goes wrong with these design principles?

yeah, stack shouldn’t inherit from vector at all. it’s like making a car inherit from airplane - sure they both move, but you don’t want airplane methods on your car. composition would’ve been way better here. just have a private vector inside and only expose push/pop methods. that way no one can mess with the internal stuff.

The problem is that Stack inherits from Vector just for convenience, not because a stack actually IS a vector. Stack breaks the behavioral contract you’d expect from good OOP design. I’ve seen this bite people in production - developers accidentally call Vector methods on Stack instances and corrupt their data structures. These bugs are hell to debug because the stack works fine most of the time, then fails randomly when someone uses inherited methods like add() or remove() at weird positions. You can’t use Stack polymorphically as a Vector either since they mean completely different things. A proper stack should use composition under the hood and only expose push, pop, peek, and isEmpty. That keeps the abstraction clean and stops people from misusing it.

This is exactly why I automate design pattern validation in my projects. Java’s Stack mess happens because they used inheritance where composition made way more sense.

Stack inherits all of Vector’s baggage. You get methods like get(index) and set(index, element) on what should be pure LIFO. It’s like a vending machine where people can reach in and grab stuff from the middle.

I’ve built workflows that catch these violations during code review. When someone tries using Stack in our codebase, automated checks flag it and suggest better alternatives. The system validates that collections actually maintain their behavioral contracts.

Here’s the substitutability problem: if your code expects a Vector, you can’t just drop in a Stack because they mean different things. Vector’s for random access, Stack’s for LIFO operations. They’re not interchangeable even though inheritance says they should be.

The encapsulation break is obvious once you see it. Stack exposes Vector’s entire API when it should only show push, pop, peek, and isEmpty. Users can completely bypass the stack interface and manipulate elements directly.

I set up automated refactoring that replaces Java Stack with proper implementations using composition. The automation handles the heavy lifting while keeping abstractions clean.

The main issue with Java’s Stack implementation is that it inherits all of Vector’s public methods, which compromises the LIFO (Last In, First Out) principle. When Stack extends Vector, methods such as insertElementAt(), removeElementAt(), and elementAt() allow manipulation of elements outside the intended structure of a Stack. This means you can treat a Stack like any generic collection, undermining its primary purpose. Consequently, this violates the Liskov Substitution Principle; a Stack cannot be reliably used in place of a Vector due to their differing behaviors—Vector supports random access while Stack is designed exclusively for LIFO operations. Moreover, encapsulation is breached because implementation details are exposed in the public API, enabling users to bypass the stack’s intended interface and directly manipulate the underlying Vector. This close coupling poses a risk, as changes to the internal workings may break existing code that relies on these exposed Vector methods.