Java Programming
Data Structures
Tree Data Structure
Coding
Algorithms

How to implement a tree data-structure in Java?

Data Structures & Algorithms practice on Codemia

Step through 300 algorithm problems with animated visualisers that show the data structure changing as the code runs.

Practice algorithms

Introduction

In Java, "tree" can mean many shapes, from a binary search tree to a general hierarchy like a file system or comment thread. If your goal is a reusable tree data structure rather than a specialized algorithmic one, the cleanest starting point is usually a generic node with a value, a parent reference, and a list of children.

Start with a General-Purpose Node

For many real applications, you want an n-ary tree, meaning each node can have zero or more children. The implementation below is small, generic, and runnable:

java
1import java.util.ArrayDeque;
2import java.util.ArrayList;
3import java.util.List;
4import java.util.Objects;
5import java.util.Optional;
6import java.util.Queue;
7
8public class TreeDemo {
9
10    static final class TreeNode<T> {
11        private final T value;
12        private TreeNode<T> parent;
13        private final List<TreeNode<T>> children = new ArrayList<>();
14
15        TreeNode(T value) {
16            this.value = value;
17        }
18
19        public T getValue() {
20            return value;
21        }
22
23        public List<TreeNode<T>> getChildren() {
24            return List.copyOf(children);
25        }
26
27        public TreeNode<T> addChild(T childValue) {
28            TreeNode<T> child = new TreeNode<>(childValue);
29            child.parent = this;
30            children.add(child);
31            return child;
32        }
33
34        public Optional<TreeNode<T>> findBreadthFirst(T target) {
35            Queue<TreeNode<T>> queue = new ArrayDeque<>();
36            queue.add(this);
37
38            while (!queue.isEmpty()) {
39                TreeNode<T> current = queue.remove();
40                if (Objects.equals(current.value, target)) {
41                    return Optional.of(current);
42                }
43                queue.addAll(current.children);
44            }
45
46            return Optional.empty();
47        }
48
49        public void printPreOrder() {
50            printPreOrder(0);
51        }
52
53        private void printPreOrder(int depth) {
54            System.out.println("  ".repeat(depth) + value);
55            for (TreeNode<T> child : children) {
56                child.printPreOrder(depth + 1);
57            }
58        }
59    }
60
61    public static void main(String[] args) {
62        TreeNode<String> root = new TreeNode<>("root");
63        TreeNode<String> docs = root.addChild("docs");
64        TreeNode<String> src = root.addChild("src");
65        docs.addChild("readme.md");
66        src.addChild("Main.java");
67        src.addChild("Utils.java");
68
69        root.printPreOrder();
70        System.out.println(root.findBreadthFirst("Utils.java").isPresent());
71    }
72}

This design is enough for many hierarchical models without committing you to binary-tree-specific rules.

Why Parent and Children References Help

Storing the parent reference is optional, but it is useful. It allows you to:

  • walk upward in the tree
  • remove a node from its parent cleanly
  • compute paths from a node back to the root

Meanwhile, the children list gives you flexible branching. That is what makes this structure suitable for menus, organization charts, parsed documents, or AST-like trees.

Traversal Is More Important Than the Container

Once the node structure exists, the next design decision is traversal.

  • depth-first traversal is good for recursive processing
  • breadth-first traversal is good for level-order search

The example above includes both a preorder walk for printing and a breadth-first search for lookup. Those two operations cover a surprising amount of everyday tree usage.

If you need binary-search-tree semantics, you would design the node differently with dedicated left and right child references. For a general tree, a list of children is the simpler and more flexible choice.

Wrapping the Root in a Tree Class

Sometimes a dedicated Tree<T> wrapper is helpful because it gives you one place for root-level operations:

java
1public final class Tree<T> {
2    private final TreeDemo.TreeNode<T> root;
3
4    public Tree(T rootValue) {
5        this.root = new TreeDemo.TreeNode<>(rootValue);
6    }
7
8    public TreeDemo.TreeNode<T> getRoot() {
9        return root;
10    }
11}

This is useful when you want future features such as serialization, validation, or tree-wide traversal helpers. If you do not need that extra abstraction yet, a root node by itself is perfectly fine.

Common Pitfalls

  • Building a general hierarchy with a binary-tree design even though nodes need many children.
  • Exposing the mutable children list and letting callers bypass tree invariants.
  • Forgetting to maintain the parent reference when adding or moving nodes.
  • Using recursion everywhere without thinking about very deep trees.
  • Confusing search order with storage order. Breadth-first and depth-first answer different questions.

Summary

  • For a general Java tree, a node with a value, parent, and children list is a strong starting point.
  • Use methods like addChild() to preserve structural consistency.
  • Breadth-first search and preorder traversal cover many common use cases.
  • Add a wrapper Tree<T> class only if tree-wide behavior needs a dedicated home.
  • Keep the internal children list encapsulated so the tree cannot be corrupted accidentally.

Related reading
Course
Intermediate
27 lessons
15 hours
DSA Fundamentals

Master algorithmic patterns and data structures through hands-on LeetCode-style problems - from arrays and hashing to dynamic programming and advanced graphs.

View the course
Track what you have practised

A free account saves your progress, solutions and study plan across every problem on Codemia.

Data Structures & Algorithms practice on Codemia

Step through 300 algorithm problems with animated visualisers that show the data structure changing as the code runs.

Practice algorithms

All Rights Reserved.