Dynamic Array in java I am implementing a class to mimic dynamic array as a part of my learning process of data structures. I have written the following class which works as a dynamic array data structure.
I have gone through some video tutorials and also read some code from Java's ArrayList implementation to understand how to code it in a simple manner. I would like to get the code reviewed from the following perspective:

Is there any design pattern that I should be following
Improvements like removing compile time warning related to generics.
Any tips on including test cases that can expose hidden issues in the code
Any other tips that will make it a better code like improve its performance or make it more modular.

import java.util.Arrays;
import java.util.Iterator;

public class DynamicArray&lt;T&gt; implements Iterable&lt;T&gt; {
  private Object[] elements;
  private int length = 0;
  private int capacity = 0;

  public DynamicArray() {
    this(10);
  }

  public DynamicArray(int capacity) {
    this.capacity = capacity;
    this.length = 0;
    this.elements = new Object[capacity];
  }

  public int size() {
    return length;
  }

  public boolean isEmpty() {
    return size() &gt; 0;
  }

  public T get(int index) {
    validateIndexValue(index);
    return (T)elements[index];
  }

  public void set(int index, T element) {
    validateIndexValue(index);
    elements[index] = element;
  }

  public void clear() {
    for(int i = 0; i &lt; length; i++) {
      elements[i] = null;
    }
    length = 0;
  }

  public boolean add(T element) {
    ensureCapacity(length + 1);
    elements[length++] = element;
    return true;
  }

  public T removeAt(int index) {
    validateIndexValue(index);
    T element = (T) elements[index];
    removeElement(index);
    return element;
  }

  public boolean remove(T element) {
    if(element == null) {
      for(int i = 0; i &lt; length; i++) {
        if(elements[i] == null) {
          removeElement(i);
          return true;
        }
      }
    } else {
      for(int i = 0; i &lt; length; i++) {
        if(element.equals(elements[i])) {
          removeElement(i);
          return false;
        }
      }
    }
    return false;
  }

  public int indexOf(T element) {
    if(element == null) return -1;
    for(int i = 0; i &lt; length; i++) {
      if(element.equals(elements[i])) {
        return i;
      }
    }
    return -1;
  }

  public boolean contains(T element) {
    return indexOf(element) &gt; -1;
  }

  public java.util.Iterator&lt;T&gt; iterator() {
    return new java.util.Iterator&lt;T&gt;() {
      int index = 0;
      public boolean hasNext() {
        return index &lt; length;
      }

      public T next() {
        return (T) elements[index++];
      }
    };
  }

  private void removeElement(int index) {
    int elementsToMove = length - index - 1;
    if(elementsToMove &gt; 0) {
      System.arraycopy(elements, index + 1, elements, index, elementsToMove);
    }
    elements[--length] = null;
  }

  private void ensureCapacity(int minSize) {
    if(minSize &gt;= capacity) {
      capacity *= 2;
      elements = Arrays.copyOf(elements, capacity);
    }
  }

  private void validateIndexValue(int index) {
    if(index &gt;= length || index &lt; 0) {
      throw new IllegalArgumentException(&quot;Bad index value passed : &quot; + index);
    }
  }
}
```
Unit testing
Definitely write some test cases for your code.  JUnit would be one possibility, but there are many test frameworks to choose from.
With any test framework, and at least one test per public method, this goof would have been immediately apparent:
  public boolean isEmpty() {
    return size() &gt; 0;
  }

As Timothy Truckle points out in a comment below, one test per public method is not enough.
Misleading method argument name/implementation
While the way that you used ensureCapacity(length + 1) will work, it hides a subtle bug.
  private void ensureCapacity(int minSize) {
    if(minSize &gt;= capacity) {
      capacity *= 2;
      elements = Arrays.copyOf(elements, capacity);
    }
  }

Consider if you exposed the method ensureCapacity(int minSize) as a public method, like the standard library's ArrayList#ensureCapacity method.  A caller might use:
  var container = new DynamicArray&lt;Integer&gt;();
  container.ensureCapacity(10000);
  // ...

The first statement creates the container, with an initial capacity of 10.  The next statement looks to resize the container to hold 10000 integers, but what actually happens is, since 10000 is larger than the current capacity, the capacity is doubled to 20.
Instead of capacity *= 2; perhaps you would want something more along the lines of:
  private void ensureCapacity(int minSize) {
    if(minSize &gt;= capacity) {
      capacity = Math.max(capacity * 2, minSize);
      elements = Arrays.copyOf(elements, capacity);
    }
  }

Initial Capacity
Even without exposing ensureCapacity(int minSize) as a public function, the following test will cause the bug to appear in a different way, causing a ArrayIndexOutOfBounds exception:
  var container = new DynamicArray&lt;Integer&gt;(0);
  container.add(10);

The initial capacity is zero.  In attempting to add an item, the length + 1 will exceed the capacity of zero, so the capacity will be doubled from zero to ... zero!  Then, elements[length++] = element; will access the first element from an array of length 0.  Oops.
You could prevent this with validation in the constructor ...
    if(capacity &lt;= 0)
      throw new IllegalArgumentException(&quot;Capacity must be positive&quot;);

With the capacity = Math.max(capacity * 2, minSize); fix, this could be relaxed to allow an initial capacity of zero:
    if(capacity &lt; 0)
      throw new IllegalArgumentException(&quot;Capacity cannot be negative&quot;);

Iteration
You define an Iterator for your DynamicArray, but if the array is modified while your iterator is in flight over the list, odd things may happen.
For example: If you have a DynamicArray&lt;String&gt; containing &quot;foo&quot;, &quot;bar&quot; and &quot;baz&quot;, you might create the iterator and retrieve the first item: &quot;foo&quot;.  If you then performed .removeElement(0), the next element returned by the iterator would be &quot;baz&quot;, not &quot;bar&quot;!
The standard collections will raise a ConcurrentModificationException in these cases.  They achieve this by maintaining a modCount in the container.  Each .add() or .remove() operation would increment this by one.  When the iterator is created, it captures the current modCount value for the container.  On every iterator operation, it checks to see if the modCount has unexpectedly changed, and if so, raise the exception.
Compile time warnings related to generics
You can add @SuppressWarnings(&quot;unchecked&quot;) annotation to a statement, method, or an entire class.  Ideally, you should apply the annotations to the smallest possible scopes to avoid accidentally suppressing warnings you didn't intend to.
See this answer