I know this question has been asked multiple times before but I'm looking for an answer based on type erasure.
Why does the compiler give an error on adding Integer to ArrayList<String>? I want to understand this by type erasure and byte code of add method in ArrayList.

Even though type erasure means that at execution time the VM can't distinguish between an ArrayList<String> and an ArrayList<Integer> (they're both just instances of ArrayList) the compiler does know about the type argument, and knows what's safe.
So if you have:
ArrayList<Integer> integers = new ArrayList<Integer>();
integers.add("foo");
then the compile-time type of integers is ArrayList<Integer>, and any operations performed on integers will be checked with that in mind. The only add calls in ArrayList are
add(E e)
add(int index, E e)
Now there's no conversion from String to Integer, so neither of those overloads is applicable for the call integers.add("foo"), hence the compile-time error.
Basically, generics provide two things:
Integer references, the compiler won't let you add a String reference to itSimplicity in terms of casts - when a method of a class with a type parameter T returns a value of type T and the compiler knows what that T is, it can insert an appropriate cast from the erased type (often Object) to the type argument. That's how:
List<Integer> integers = getListFromSomewhere();
Integer x = integers.get(0);
works - the compiler adds a cast in the calling code. (That cast is still checked at execution time.)
See more on this question at Stackoverflow