diff --git a/test/hotspot/jtreg/compiler/intrinsics/string/TestStringIntrinsics.java b/test/hotspot/jtreg/compiler/intrinsics/string/TestStringIntrinsics.java
index 36cf6827b9b..65984029397 100644
--- a/test/hotspot/jtreg/compiler/intrinsics/string/TestStringIntrinsics.java
+++ b/test/hotspot/jtreg/compiler/intrinsics/string/TestStringIntrinsics.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2015, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2015, 2020, Oracle and/or its affiliates. All rights reserved.
  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
  *
  * This code is free software; you can redistribute it and/or modify it
@@ -25,12 +25,16 @@
  * @test
  * @bug 8054307
  * @summary Tests correctness of string related intrinsics and C2 optimizations.
+ * @library /test/lib
  *
  * @run main/timeout=240 compiler.intrinsics.string.TestStringIntrinsics
  */
 
 package compiler.intrinsics.string;
 
+import jdk.test.lib.format.Format;
+import jdk.test.lib.format.ArrayCodec;
+
 import java.lang.annotation.ElementType;
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
@@ -77,8 +81,8 @@ public class TestStringIntrinsics {
         for (Method m : TestStringIntrinsics.class.getMethods()) {
             if (m.isAnnotationPresent(Test.class)) {
                 System.out.print("Checking " + m.getName() + "... ");
-                Operation op = m.getAnnotation(Test.class).op();
                 Test antn = m.getAnnotation(Test.class);
+                Operation op = antn.op();
                 if (isStringConcatTest(op)) {
                     checkStringConcat(op, m, antn);
                 } else {
@@ -121,13 +125,13 @@ public class TestStringIntrinsics {
 
             switch (op) {
             case ARR_EQUALS_B:
-                invokeAndCheck(m, (incL == 0), latin1.getBytes("ISO-8859-1"), latin1Copy.getBytes("ISO-8859-1"));
-                invokeAndCheck(m, true, new byte[] {1, 2, 3}, new byte[] {1, 2, 3});
-                invokeAndCheck(m, true, new byte[] {1}, new byte[] {1});
-                invokeAndCheck(m, true, new byte[] {}, new byte[] {});
+                invokeAndCompareArrays(m, (incL == 0), latin1.getBytes("ISO-8859-1"), latin1Copy.getBytes("ISO-8859-1"));
+                invokeAndCompareArrays(m, true, new byte[] {1, 2, 3}, new byte[] {1, 2, 3});
+                invokeAndCompareArrays(m, true, new byte[] {1}, new byte[] {1});
+                invokeAndCompareArrays(m, true, new byte[] {}, new byte[] {});
                 break;
             case ARR_EQUALS_C:
-                invokeAndCheck(m, (incU == 0), utf16.toCharArray(), arrU);
+                invokeAndCompareArrays(m, (incU == 0), utf16.toCharArray(), arrU);
                 break;
             case EQUALS:
                 invokeAndCheck(m, (incL == 0), latin1, latin1Copy);
@@ -240,18 +244,49 @@ public class TestStringIntrinsics {
         }
     }
 
+    /**
+     * Invokes method 'm' by passing arguments the two 'args' (which are supposed to be arrays)
+     * checks if the returned value. In case of error and arrays being not equal, prints their difference.
+     */
+    private void invokeAndCompareArrays(Method m, boolean expectedResult, Object arg0, Object arg1) throws Exception {
+        boolean result = (Boolean)m.invoke(null, arg0, arg1);
+        if (expectedResult == result)
+            return;
+
+        String cause = String.format("Result: (%b) of '%s' is not equal to expected (%b)",
+                        result, m.getName(), expectedResult);
+
+        if (expectedResult == true) {
+            System.err.println(cause);
+            System.err.println(Format.arrayDiff(arg0, arg1));
+        } else {
+            System.err.println(cause);
+            System.err.printf("First array argument: %n    %s%n", ArrayCodec.format(arg0));
+        }
+
+        throw new RuntimeException(cause);
+    }
+
     /**
      * Invokes method 'm' by passing arguments 'args' and checks if the
      * returned value equals 'expectedResult'.
      */
     private void invokeAndCheck(Method m, Object expectedResult, Object... args) throws Exception {
-        Object result = m.invoke(null, args);
-        if (!result.equals(expectedResult)) {
-//            System.out.println("Expected:");
-//            System.out.println(expectedResult);
-//            System.out.println("Returned:");
-//            System.out.println(result);
-            throw new RuntimeException("Result of '" + m.getName() + "' not equal to expected value.");
+        Object actualResult = m.invoke(null, args);
+        if (!actualResult.equals(expectedResult)) {
+            var nl = System.lineSeparator();
+            StringBuilder msgBuilder = new StringBuilder();
+            msgBuilder.append("Actual result of '" + m.getName() + "' is not equal to expected value." + nl);
+            msgBuilder.append("Expected: " + Format.asLiteral(expectedResult) + nl);
+            msgBuilder.append("Actual: " + Format.asLiteral(actualResult));
+
+            for (int i = 0; i < args.length; i++) {
+                msgBuilder.append(nl + "    Arg" + i + ": " + Format.asLiteral(args[i]));
+            }
+
+            final String message = msgBuilder.toString();
+            System.err.println(message);
+            throw new RuntimeException(message);
         }
     }
 
diff --git a/test/lib-test/jdk/test/lib/format/ArrayDiffTest.java b/test/lib-test/jdk/test/lib/format/ArrayDiffTest.java
new file mode 100644
index 00000000000..7b7dfc086f1
--- /dev/null
+++ b/test/lib-test/jdk/test/lib/format/ArrayDiffTest.java
@@ -0,0 +1,409 @@
+/*
+ * Copyright (c) 2020, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+package jdk.test.lib.format;
+
+import org.testng.annotations.Test;
+
+import static org.testng.Assert.assertTrue;
+import static org.testng.Assert.assertFalse;
+import static org.testng.Assert.assertEquals;
+
+/*
+ * @test
+ * @summary Check ArrayDiff formatting
+ * @library /test/lib
+ * @run testng jdk.test.lib.format.ArrayDiffTest
+ */
+public class ArrayDiffTest {
+
+    @Test
+    public void testEqualArrays() {
+        char[] first = new char[]  {'a', 'b', 'c', 'd', 'e', 'f', 'g'};
+        char[] second = new char[] {'a', 'b', 'c', 'd', 'e', 'f', 'g'};
+
+        assertTrue(ArrayDiff.of(first, second).areEqual());
+    }
+
+    @Test
+    public void testOutputFitsWidth() {
+        new AssertBuilder()
+            .withDefaultParams()
+            .withArrays(
+                new byte[] {7, 8, 9, 10,  11, 12, 13},
+                new byte[] {7, 8, 9, 10, 125, 12, 13})
+            .thatResultIs(false)
+            .thatFormattedValuesAre(
+                4,
+                "[7, 8, 9, 10,  11, 12, 13]",
+                "[7, 8, 9, 10, 125, 12, 13]",
+                "             ^^^^")
+            .assertTwoWay();
+    }
+
+    @Test
+    public void testIntegers() {
+        new AssertBuilder()
+            .withDefaultParams()
+            .withArrays(
+                new int[] {7, 8, 10, 11, 12},
+                new int[] {7, 8, 9, 10, 11, 12, 13})
+            .thatResultIs(false)
+            .thatFormattedValuesAre(
+                2,
+                "[7, 8, 10, 11, 12]",
+                "[7, 8,  9, 10, 11, 12, 13]",
+                "      ^^^")
+            .assertTwoWay();
+    }
+
+    @Test
+    public void testLongs() {
+        new AssertBuilder()
+            .withDefaultParams()
+            .withArrays(
+                new long[] {1, 2, 3, 4},
+                new long[] {1, 2, 3, 10})
+            .thatResultIs(false)
+            .thatFormattedValuesAre(
+                3,
+                "[1, 2, 3,  4]",
+                "[1, 2, 3, 10]",
+                "         ^^^")
+            .assertTwoWay();
+    }
+
+    @Test
+    public void testFirstElementIsWrong() {
+        new AssertBuilder()
+            .withDefaultParams()
+            .withArrays(
+                new byte[] {122},
+                new byte[] {7, 8, 9, 10, 125, 12, 13})
+            .thatResultIs(false)
+            .thatFormattedValuesAre(
+                0,
+                "[122]",
+                "[  7, 8, 9, 10, 125, 12, 13]",
+                " ^^^")
+            .assertTwoWay();
+    }
+
+    @Test
+    public void testOneElementIsEmpty() {
+        new AssertBuilder()
+            .withDefaultParams()
+            .withArrays(
+                new byte[] {7, 8, 9, 10, 125, 12, 13},
+                new byte[] {})
+            .thatResultIs(false)
+            .thatFormattedValuesAre(
+                0,
+                "[7, 8, 9, 10, 125, 12, 13]",
+                "[]",
+                " ^")
+            .assertTwoWay();
+    }
+
+    @Test
+    public void testOutputDoesntFitWidth() {
+        new AssertBuilder()
+            .withParams(20, Integer.MAX_VALUE)
+            .withArrays(
+                new char[] {'1', '2', '3', '4', '5', '6', '7'},
+                new char[] {'1', 'F', '3', '4', '5', '6', '7'})
+            .thatResultIs(false)
+            .thatFormattedValuesAre(
+                1,
+                "[1, 2, 3, 4, 5, ...",
+                "[1, F, 3, 4, 5, ...",
+                "   ^^")
+            .assertTwoWay();
+    }
+
+    @Test
+    public void testVariableElementWidthOutputDoesntFitWidth() {
+        new AssertBuilder()
+            .withParams(20, Integer.MAX_VALUE)
+            .withArrays(
+                new byte[] {1,   2, 3, 4, 5, 6, 7},
+                new byte[] {1, 112, 3, 4, 5, 6, 7})
+            .thatResultIs(false)
+            .thatFormattedValuesAre(
+                1,
+                "[1,   2, 3, 4, 5, ...",
+                "[1, 112, 3, 4, 5, ...",
+                "   ^^^^")
+            .assertTwoWay();
+    }
+
+    @Test
+    public void testContextBefore() {
+        new AssertBuilder()
+            .withParams(20, 2)
+            .withArrays(
+                new char[] {'1', '2', '3', '4', '5', '6', '7'},
+                new char[] {'1', '2', '3', '4', 'F', '6', '7'})
+            .thatResultIs(false)
+            .thatFormattedValuesAre(
+                4,
+                "... 3, 4, 5, 6, 7]",
+                "... 3, 4, F, 6, 7]",
+                "         ^^")
+            .assertTwoWay();
+    }
+
+    @Test
+    public void testBoundedBytesWithDifferentWidth() {
+        new AssertBuilder()
+            .withParams(24, 2)
+            .withArrays(
+                new byte[] {0, 1, 2, 3, 125, 5, 6, 7},
+                new byte[] {0, 1, 2, 3,   4, 5, 6, 7})
+            .thatResultIs(false)
+            .thatFormattedValuesAre(
+                4,
+                "... 2, 3, 125, 5, 6, 7]",
+                "... 2, 3,   4, 5, 6, 7]",
+                "         ^^^^")
+            .assertTwoWay();
+    }
+
+    @Test
+    public void testBoundedFirstElementIsWrong() {
+        new AssertBuilder()
+            .withParams(25, 2)
+            .withArrays(
+                new byte[] {101, 102, 103, 104, 105, 110},
+                new byte[] {2})
+            .thatResultIs(false)
+            .thatFormattedValuesAre(
+                0,
+                "[101, 102, 103, 104, ...",
+                "[  2]",
+                " ^^^")
+            .assertTwoWay();
+    }
+
+    @Test
+    public void testBoundedOneArchiveIsEmpty() {
+        new AssertBuilder()
+            .withParams(10, 2)
+            .withArrays(
+                new char[] {'a', 'b', 'c', 'd', 'e'},
+                new char[] {})
+            .thatResultIs(false)
+            .thatFormattedValuesAre(
+                0,
+                "[a, b, ...",
+                "[]",
+                " ^")
+            .assertTwoWay();
+    }
+
+    @Test
+    public void testUnboundedOneArchiveIsEmpty() {
+        new AssertBuilder()
+            .withDefaultParams()
+            .withArrays(
+                new char[] {'a', 'b', 'c', 'd', 'e'},
+                new char[] {})
+            .thatResultIs(false)
+            .thatFormattedValuesAre(
+                0,
+                "[a, b, c, d, e]",
+                "[]",
+                " ^")
+            .assertTwoWay();
+    }
+
+    @Test
+    public void testUnprintableCharFormatting() {
+        new AssertBuilder()
+            .withDefaultParams()
+            .withArrays(
+                new char[] {0, 1, 2, 3, 4, 5, 6,   7, 8, 9, 10, 11, 12, 13, 14, 15, 16},
+                new char[] {0, 1, 2, 3, 4, 5, 6, 125, 8, 9, 10, 11, 12, 13, 14, 15, 16})
+            .thatResultIs(false)
+            .thatFormattedValuesAre(
+                7,
+                "... \\u0005, \\u0006, \\u0007, \\u0008, \\u0009, \\n, \\u000B, \\u000C, \\r, \\u000E, ...",
+                "... \\u0005, \\u0006,      }, \\u0008, \\u0009, \\n, \\u000B, \\u000C, \\r, \\u000E, ...",
+                "                   ^^^^^^^")
+            .assertTwoWay();
+    }
+
+    @Test
+    public void testStringElements() {
+        new AssertBuilder()
+            .withDefaultParams()
+            .withArrays(
+                new String[]  {"first", "second", "third", "u\nprintable"},
+                new String[] {"first", "second", "incorrect", "u\nprintable"})
+            .thatResultIs(false)
+            .thatFormattedValuesAre(
+                2,
+                "[\"first\", \"second\",     \"third\", \"u\\nprintable\"]",
+                "[\"first\", \"second\", \"incorrect\", \"u\\nprintable\"]",
+                "                   ^^^^^^^^^^^^")
+            .assertTwoWay();
+    }
+
+    @Test
+    public void testToStringableObjects() {
+        class StrObj {
+            private final String value;
+            public boolean equals(Object another) { return ((StrObj)another).value.equals(value); }
+            public StrObj(String value) { this.value = value; }
+            public String toString() { return value; }
+        }
+
+        new AssertBuilder()
+            .withDefaultParams()
+            .withArrays(
+                new StrObj[] {new StrObj("1"), new StrObj("Unp\rintable"), new StrObj("5")},
+                new StrObj[] {new StrObj("1"), new StrObj("2"),            new StrObj("5")})
+            .thatResultIs(false)
+            .thatFormattedValuesAre(
+                1,
+                "[1, Unp\\rintable, 5]",
+                "[1,            2, 5]",
+                "   ^^^^^^^^^^^^^")
+            .assertTwoWay();
+    }
+
+    @Test
+    public void testNullElements() {
+        new AssertBuilder()
+            .withDefaultParams()
+            .withArrays(
+                new String[] {"Anna", null,   "Bill",    "Julia"},
+                new String[] {"Anna", "null", "William", "Julia"})
+            .thatResultIs(false)
+            .thatFormattedValuesAre(
+                1,
+                "[\"Anna\",   null,    \"Bill\", \"Julia\"]",
+                "[\"Anna\", \"null\", \"William\", \"Julia\"]",
+                "        ^^^^^^^")
+            .assertTwoWay();
+    }
+
+    @Test (expectedExceptions = NullPointerException.class)
+    public void testFirstArrayIsNull() {
+        var diff = ArrayDiff.of(null, new String[] {"a", "b"});
+    }
+
+    @Test (expectedExceptions = NullPointerException.class)
+    public void testSecondArrayIsNull() {
+        var diff = ArrayDiff.of(null, new String[] {"a", "b"});
+    }
+
+    class AssertBuilder {
+        private boolean defaultParameters;
+        private int width;
+        private int contextBefore;
+        private Object firstArray;
+        private Object secondArray;
+        private boolean expectedResult;
+        private int expectedIndex;
+        private String firstFormattedArray;
+        private String secondFormattedArray;
+        private String failureMark;
+
+        public AssertBuilder withDefaultParams() {
+            defaultParameters = true;
+            return this;
+        }
+
+        public AssertBuilder withParams(int width, int contextBefore) {
+            defaultParameters = false;
+            this.width = width;
+            this.contextBefore = contextBefore;
+            return this;
+        }
+
+        public AssertBuilder withArrays(Object first, Object second) {
+            firstArray = first;
+            secondArray = second;
+            return this;
+        }
+
+        public AssertBuilder thatResultIs(boolean result) {
+            expectedResult = result;
+            return this;
+        }
+
+        public AssertBuilder thatFormattedValuesAre(
+                int idx, String first, String second, String mark) {
+            expectedIndex = idx;
+            firstFormattedArray = first;
+            secondFormattedArray = second;
+            failureMark = mark;
+            return this;
+        }
+
+        public void assertTwoWay() {
+            ArrayDiff diff;
+
+            // Direct
+            if (defaultParameters) {
+                diff = ArrayDiff.of(firstArray, secondArray);
+            } else {
+                diff = ArrayDiff.of(firstArray, secondArray, width, contextBefore);
+            }
+
+            if (expectedResult == true) {
+                assertTrue(diff.areEqual());
+            } else {
+                String expected = String.format(
+                    "Arrays differ starting from [index: %d]:%n" +
+                    "%s%n" + "%s%n" + "%s",
+                    expectedIndex, firstFormattedArray, secondFormattedArray, failureMark);
+
+                assertFalse(diff.areEqual());
+                assertEquals(diff.format(), expected);
+            }
+
+            // Reversed
+            if (defaultParameters) {
+                diff = ArrayDiff.of(secondArray, firstArray);
+            } else {
+                diff = ArrayDiff.of(secondArray, firstArray, width, contextBefore);
+            }
+
+            if (expectedResult == true) {
+                assertTrue(diff.areEqual());
+            } else {
+                String expected = String.format(
+                    "Arrays differ starting from [index: %d]:%n" +
+                    "%s%n" + "%s%n" + "%s",
+                    expectedIndex, secondFormattedArray, firstFormattedArray, failureMark);
+
+                assertFalse(diff.areEqual());
+                assertEquals(diff.format(), expected);
+            }
+        }
+
+    }
+
+}
diff --git a/test/lib/jdk/test/lib/format/ArrayCodec.java b/test/lib/jdk/test/lib/format/ArrayCodec.java
new file mode 100644
index 00000000000..0e34e7cd811
--- /dev/null
+++ b/test/lib/jdk/test/lib/format/ArrayCodec.java
@@ -0,0 +1,340 @@
+/*
+ * Copyright (c) 2020, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+package jdk.test.lib.format;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Optional;
+
+/**
+ * A codec helping representing arrays in a string form.
+ *
+ * Encoding can be done in a controllable fashion (allowing the user to encode two
+ * or more arrays in a time) or as a single operation.
+ */
+public class ArrayCodec<E> {
+    private static final String ELLIPSIS = "...";
+
+    private boolean exhausted;
+    private StringBuilder encoded;
+
+    private List<E> source;
+    private String element;
+
+    private boolean bounded = false;
+    private int maxWidth;
+    private int idx;
+
+    private ArrayCodec(List<E> source) {
+        this.source = source;
+    }
+
+    /**
+     * Creates a codec for a char array
+     *
+     * @param array source array
+     * @return an ArrayCodec for the provided array
+     */
+    public static ArrayCodec<Character> of(char[] array) {
+        var source = new ArrayList<Character>(array.length);
+        for (char value: array) {
+            source.add(value);
+        }
+        return new ArrayCodec<>(source);
+    }
+
+    /**
+     * Creates a codec for a byte array
+     *
+     * @param array source array
+     * @return an ArrayCodec for the provided array
+     */
+    public static ArrayCodec<Byte> of(byte[] array) {
+        var source = new ArrayList<Byte>(array.length);
+        for (byte value: array) {
+            source.add(value);
+        }
+        return new ArrayCodec<>(source);
+    }
+
+    /**
+     * Creates a codec for an int array
+     *
+     * @param array source array
+     * @return an ArrayCodec for the provided array
+     */
+    public static ArrayCodec<Integer> of(int[] array) {
+        var source = new ArrayList<Integer>(array.length);
+        for (int value: array) {
+            source.add(value);
+        }
+        return new ArrayCodec<>(source);
+    }
+
+    /**
+     * Creates a codec for a long array
+     *
+     * @param array source array
+     * @return an ArrayCodec for the provided array
+     */
+    public static ArrayCodec<Long> of(long[] array) {
+        var source = new ArrayList<Long>(array.length);
+        for (long value: array) {
+            source.add(value);
+        }
+        return new ArrayCodec<>(source);
+    }
+
+    /**
+     * Creates a codec for a String array
+     *
+     * @param array source array
+     * @return an ArrayCodec for the provided array
+     */
+    public static ArrayCodec<String> of(String[] array) {
+        var source = new ArrayList<String>(array.length);
+        for (String value: array) {
+            source.add(value);
+        }
+        return new ArrayCodec<>(source);
+    }
+
+    /**
+     * Creates a codec for a generic Object array
+     *
+     * @param array source array
+     * @return an ArrayCodec for the provided array
+     */
+    public static ArrayCodec<Object> of(Object[] array) {
+        var source = new ArrayList<Object>(array.length);
+        for (Object value: array) {
+            source.add(value);
+        }
+        return new ArrayCodec<Object>(source);
+    }
+
+    /**
+     * Creates a codec for a generic array, trying to recognize its component type
+     *
+     * @param array source array
+     * @throws IllegalArgumentException if {@code array}'s component type is not supported
+     * @return an ArrayCodec for the provided array
+     */
+    public static ArrayCodec of(Object array) {
+        var type = array.getClass().getComponentType();
+        if (type == byte.class) {
+            return ArrayCodec.of((byte[])array);
+        } else if (type == int.class) {
+            return ArrayCodec.of((int[])array);
+        } else if (type == long.class) {
+            return ArrayCodec.of((long[])array);
+        } else if (type == char.class) {
+            return ArrayCodec.of((char[])array);
+        } else if (type == String.class) {
+            return ArrayCodec.of((String[])array);
+        } else if (!type.isPrimitive() && !type.isArray()) {
+            return ArrayCodec.of((Object[])array);
+        }
+
+        throw new IllegalArgumentException("Unsupported array component type: " + type);
+    }
+
+    /**
+     * Formats an array at-once.
+     * The array is enclosed in brackets, its elements are separated with
+     * commas. String elements are additionally surrounded by double quotes.
+     * Unprintable symbols are C-stye escaped.
+     *
+     * <p>Sample outputs:
+     *
+     * <pre>
+     *   [0, 1, 2, 3, 4]
+     *   ["one", "first", "tree"]
+     *   [object1, object2, object3]
+     *   [a, b, \n, &#92;u0002/, c]
+     * </pre>
+     *
+     * @throws IllegalArgumentException if {@code array}'s component type is not supported
+     * @return an ArrayCodec for the provided array
+     */
+    public static String format(Object array) {
+        var codec = ArrayCodec.of(array);
+        codec.startFormatting(0, -1);
+        while (!codec.isExhausted()) {
+            codec.formatNext();
+            codec.appendFormatted();
+        }
+        return codec.getEncoded();
+    }
+
+    /**
+     * Starts formatting with the given parameters.
+     *
+     * @param startIdx first element's index to start formattig with
+     * @param maxWidth maximum allowed formatting width (in characters).
+     * @return an ArrayCodec for the provided array
+     */
+    public void startFormatting(int startIdx, int maxWidth) {
+        encoded = new StringBuilder(startIdx == 0 ? "[" : ELLIPSIS);
+        exhausted = false;
+        this.maxWidth = maxWidth;
+        bounded = (maxWidth > 0);
+        idx = startIdx;
+    }
+
+    /**
+     * Format next element, store it in the internal element storage.
+     */
+    public void formatNext() {
+        int limit = source.size();
+
+        String prefix = idx == 0 || idx >= limit ? "" : " ";
+        String suffix = (idx + 1 == limit) || (source.isEmpty() && idx == 0)
+                ? "]"
+                : idx >= limit ? "" : ",";
+        element = prefix +
+                (idx >= limit ? "" : Format.asLiteral(source.get(idx)))
+                + suffix;
+    }
+
+    /**
+     * Append formatted element to internal StringBuilder.
+     *
+     * The formatted-so-far string can be accessed via {@link #getEncoded}
+     * no elements in array left the method silently does nothing.
+     */
+    public void appendFormatted() {
+        if (exhausted) {
+            return;
+        }
+
+        boolean isLast = idx == source.size() - 1;
+        if (isLast || source.isEmpty()) {
+            exhausted = true;
+        }
+
+        if (bounded && encoded.length() + element.length() > maxWidth - ELLIPSIS.length()) {
+            encoded.append(isLast ? element : " " + ELLIPSIS);
+            exhausted = true;
+        } else {
+            encoded.append(element);
+        }
+        idx++;
+    }
+
+    /**
+     * Aligns the element by another codec.
+     *
+     * If another codec's last encoded element string is longer than this
+     * codec's, widens this codec's encoded element with spaces so the
+     * two strings have the same length;
+     *
+     * @param another Another codec to compare encoded element width with
+     */
+    public void alignBy(ArrayCodec<E> another) {
+        if (!element.equals("") && !element.equals("]")) {
+            int delta = another.element.length() - element.length();
+            if (delta > 0) {
+                element = Format.paddingForWidth(delta) + element;
+            }
+        }
+    }
+
+    /**
+     * Indicates if there are no elements left in the source array
+     *
+     * @return {@code true} if there are no elements left, {@code false} otherwise
+     */
+    public boolean isExhausted() {
+        return exhausted;
+    }
+
+    /**
+     * Returns the string encoded-so-far
+     *
+     * @return the string encoded-so-far
+     */
+    public String getEncoded() {
+        return encoded.toString();
+    }
+
+    /**
+     * Returns the length of the string encoded-so-far
+     *
+     * @return the length of the string encoded-so-far
+     */
+    public int getEncodedLength() {
+        return encoded.length();
+    }
+
+    /**
+     * Returns the length of the last encoded element
+     *
+     * @return the length of the last encoded element
+     */
+    public int getElementLength() {
+        return element.length();
+    }
+
+    /**
+     * Finds and returns the first mismatch index in another codec
+     *
+     * @param another a codec mismatch with whom is to be found
+     * @return the first mismatched element's index or -1 if arrays are identical
+     */
+    public int findMismatchIndex(ArrayCodec<E> another) {
+        int result = 0;
+        while ((source.size() > result) && (another.source.size() > result)) {
+            Object first = source.get(result);
+            Object second = another.source.get(result);
+
+            if (first == null || second == null) {
+                if (first == null && second == null) {
+                    continue;   // Both elements are null (i.e. equal)
+                } else {
+                    return result;  // Only one element is null, here's the failure index
+                }
+            }
+
+            if (!first.equals(second)) {
+                return result;
+            }
+
+            result++;
+        }
+
+        return source.size() != another.source.size()
+            ? result    // Lengths are different, but the shorter arrays is a preffix to the longer array.
+            : -1;       // Arrays are identical, there's no mismatch index
+    }
+
+    /**
+     * Indicates whether source array for another codec is equal to this codec's array
+     *
+     * @return {@code true} if source arrays are equal, {@code false} otherwise
+     */
+    public boolean equals(ArrayCodec<E> another) {
+        return source.equals(another.source);
+    }
+}
diff --git a/test/lib/jdk/test/lib/format/ArrayDiff.java b/test/lib/jdk/test/lib/format/ArrayDiff.java
new file mode 100644
index 00000000000..6755a693b7c
--- /dev/null
+++ b/test/lib/jdk/test/lib/format/ArrayDiff.java
@@ -0,0 +1,207 @@
+/*
+ * Copyright (c) 2020, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+package jdk.test.lib.format;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Objects;
+import java.util.Optional;
+
+/**
+ * A difference between two arrays, which can be pretty formatted.
+ * For the calculated difference, user can request if the two arrays
+ * are equal (in terms of {@link Object#equals Object.equals()} for their
+ * elements). For the arrays that differ, a human-readable difference can
+ * be provided.
+ *
+ * <p>The difference is represented as a four-line text block, comprising of the
+ * first different element index, arrays printouts in the difference area,
+ * and a difference mark. For Primitive and Object elements in the source
+ * arrays their C-style escaped {@link String#valueOf String.valueOf()} are
+ * printed, element in String[] arrays are additionally surrounded with quotes.
+ * Additional formatting parameters, like maximum allowed width and number of
+ * elements printed before difference, can be specified.
+ *
+ * <p>Output examples:
+ *
+ * <p> two int arrays: </p>
+ * <pre>
+ * Arrays differ starting from [index: 4]:
+ *         ... 3, 4,   5, 6, 7]
+ *         ... 3, 4, 225, 6, 7]
+ *                  ^^^^
+ * </pre>
+ * <p> two String arrays: </p>
+ * <pre>
+ * Arrays differ starting from [index: 2]:
+ *         ["first", "second",     "third", "u\nprintable"]
+ *         ["first", "second", "incorrect", "u\nprintable"]
+ *                            ^^^^^^^^^^^^
+ * </pre>
+ * <p> two char arrays arrays: </p>
+ * <pre>
+ * Arrays differ starting from [index: 7]:
+ *         ... &#92;u0001, &#92;u0002, &#92;u0007, a, b, \n, ...
+ *         ... &#92;u0001, &#92;u0002,      }, a, b, \n, ...
+ *                            ^^^^^^^
+ * </pre>
+ */
+public class ArrayDiff<E> implements Diff {
+
+    private int failureIdx;
+    private final int maxWidth;
+    private final int contextBefore;
+
+    private final ArrayCodec<E> first;
+    private final ArrayCodec<E> second;
+
+    private ArrayDiff(ArrayCodec<E> first, ArrayCodec<E> second,
+                      int width, int getContextBefore) {
+        this.first = first;
+        this.second = second;
+        this.maxWidth = width;
+        this.contextBefore = getContextBefore;
+        failureIdx = first.findMismatchIndex(second);
+    }
+
+    /**
+     * Creates an ArrayDiff fom two arrays and default limits. The given arguments must be of the same
+     * component type.
+     *
+     * @param first the first array
+     * @param second the second array
+     * @return an ArrayDiff instance for the two arrays
+     */
+    public static ArrayDiff of(Object first, Object second) {
+        return ArrayDiff.of(first, second, Diff.Defaults.WIDTH, Diff.Defaults.CONTEXT_BEFORE);
+    }
+
+    /**
+     * Creates an ArrayDiff fom two arrays with the given limits. The given arguments must be of the same
+     * component type.
+     *
+     * @param first the first array
+     * @param second the second array
+     * @param width the maximum allowed width in characters for the formatting
+     * @param contextBefore maximum number of elements to print before those that differ
+     * @throws IllegalArgumentException if component types of arrays is not supported or are not the same
+     * @throws NullPointerException if at least one of the arrays is null
+     * @return an ArrayDiff instance for the two arrays and formatting parameters provided
+     */
+    public static ArrayDiff of(Object first, Object second, int width, int contextBefore) {
+        Objects.requireNonNull(first);
+        Objects.requireNonNull(second);
+
+        boolean bothAreArrays = first.getClass().isArray() && second.getClass().isArray();
+        boolean componentTypesAreSame =
+            first.getClass().getComponentType() == second.getClass().getComponentType();
+
+        if (!bothAreArrays || !componentTypesAreSame) {
+            throw new IllegalArgumentException("Both arguments should be arrays of the same type");
+        }
+
+        return new ArrayDiff(
+                ArrayCodec.of(first),
+                ArrayCodec.of(second),
+                width, contextBefore);
+    }
+
+    /**
+     * Formats the given diff.
+     *
+     * @return formatted difference representation.
+     */
+    @Override
+    public String format() {
+        if (areEqual()) {
+            return "";
+        }
+
+        return format(false)
+                .orElseGet(() -> format(true).get());
+    }
+
+    /**
+     * Indicates whether the two source arrays are equal
+     *
+     * @return {@code true} if the arrays are different, {@code false} otherwise
+     */
+    @Override
+    public boolean areEqual() {
+        return first.equals(second);
+    }
+
+    private void extractAndAlignElements() {
+        first.formatNext();
+        second.formatNext();
+
+        first.alignBy(second);
+        second.alignBy(first);
+    }
+
+    private static String failureMarkForWidth(int width) {
+        return new String("^").repeat(width);
+    }
+
+    private Optional<String> format(boolean bounded) {
+        int idx = bounded ? Math.max(0, failureIdx - contextBefore) : 0;
+
+        first.startFormatting(idx, bounded ? maxWidth : -1);
+        second.startFormatting(idx, bounded ? maxWidth : -1);
+        StringBuilder failureMark = new StringBuilder(
+                Format.paddingForWidth(first.getEncodedLength()));
+
+        for (; !(first.isExhausted() && second.isExhausted()); idx++) {
+            extractAndAlignElements();
+
+            first.appendFormatted();
+            second.appendFormatted();
+
+            { // Process failure mark
+                if (idx < failureIdx) {
+                    failureMark.append(Format.paddingForWidth(first.getElementLength()));
+                } else  if (idx == failureIdx) {
+                    int markLength = Math.max(first.getElementLength(), second.getElementLength()) - 1;
+                    failureMark.append(failureMarkForWidth(markLength));
+                }
+            }
+
+            final int maxEncodedLength = Math.max(
+                    first.getEncodedLength(),
+                    second.getEncodedLength());
+            if (!bounded && maxEncodedLength > maxWidth) {
+                return Optional.empty();
+            }
+        }
+
+        return Optional.of(String.format(
+                "Arrays differ starting from [index: %d]:%n%s%n%s%n%s",
+                failureIdx,
+                first.getEncoded(),
+                second.getEncoded(),
+                failureMark.toString()));
+    }
+
+}
+
diff --git a/test/lib/jdk/test/lib/format/Diff.java b/test/lib/jdk/test/lib/format/Diff.java
new file mode 100644
index 00000000000..02ed3430e5d
--- /dev/null
+++ b/test/lib/jdk/test/lib/format/Diff.java
@@ -0,0 +1,58 @@
+/*
+ * Copyright (c) 2020, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+/*
+ * Represents a possible difference between two objects.
+ */
+package jdk.test.lib.format;
+
+/**
+ * An abstraction representing formattable difference between two or more objects
+ */
+public interface Diff {
+
+    /**
+     * Default limits for formatters
+     */
+    public static class Defaults {
+        private Defaults() {  }  // This class should not be instantiated
+        public final static int WIDTH = 80;
+        public final static int CONTEXT_BEFORE = 2;
+    }
+
+    /**
+     * Formats the given diff. Different implementations can provide different
+     * result and formatting style.
+     *
+     * @return formatted difference representation.
+     */
+    String format();
+
+    /**
+     * Indicates whether the two source arrays are equal. Different
+     * implementations can treat this notion differently.
+     *
+     * @return {@code true} if the source objects are different, {@code false} otherwise
+     */
+    boolean areEqual();
+}
diff --git a/test/lib/jdk/test/lib/format/Format.java b/test/lib/jdk/test/lib/format/Format.java
new file mode 100644
index 00000000000..9770e33c232
--- /dev/null
+++ b/test/lib/jdk/test/lib/format/Format.java
@@ -0,0 +1,127 @@
+/*
+ * Copyright (c) 2020, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+package jdk.test.lib.format;
+
+/**
+ * A collection of formatting utilities
+ */
+public class Format {
+
+    /**
+     * Formats character as literal, using C-style escaping for unprintable symbols
+     *
+     * @param c character to format
+     * @return formatted string representation of the character
+     */
+    public static String asLiteral(char c) {
+        StringBuilder sb = new StringBuilder();
+        appendCharToSb(c, sb);
+        return sb.toString();
+    }
+
+    /**
+     * Escapes String in C-style
+     *
+     * @param src source string
+     * @return C-style escaped source string
+     */
+    public static String escapeString(String src) {
+        StringBuilder sb = new StringBuilder();
+        src.chars().forEachOrdered(
+                (c) -> appendCharToSb((char) c, sb));
+        return sb.toString();
+    }
+
+    /**
+     * Formats Object as literal, using its String representation C-style escaped.
+     *
+     * @param o object to format
+     * @return C-style escaped String representation of the object
+     */
+    public static String asLiteral(Object o) {
+        if (o instanceof String) {
+            return '"' + escapeString((String)o) + '"';
+        } else if (o instanceof Character) {
+            return asLiteral((char) o);
+        } else if (o instanceof Byte) {
+            return String.valueOf(o);
+        } else {
+            return escapeString(String.valueOf(o));
+        }
+    }
+
+    /**
+     * Formats a difference between two arrays with index of the first mismatch element,
+     * and slices of arrays necessary to understand the problem, along with a failure mark.
+     *
+     * @param first first array to compare
+     * @param second second array to compare
+     * @return the difference, generated by the {@link ArrayDiff ArrayDiff}
+     */
+    public static String arrayDiff(Object first, Object second) {
+        return ArrayDiff.of(first, second).format();
+    }
+
+    /**
+     * Formats a difference between two arrays with index of the first mismatch element,
+     * and slices of arrays necessary to understand the problem, along with a failure mark.
+     * Takes into account maximum allowed width and context (in elements) before the mismatch.
+     *
+     * @param first first array to compare
+     * @param second second array to compare
+     * @param width the maximum allowed width in characters for the formatting
+     * @param contextBefore maximum number of elements to print before those that differ
+     * @return the difference, generated by the {@link ArrayDiff ArrayDiff}
+     */
+    public static String arrayDiff(Object first, Object second, int width, int contextBefore) {
+        return ArrayDiff.of(first, second, width, contextBefore).format();
+    }
+
+
+    /**
+     * Returns a string of spaces with length specified.
+     *
+     * @param width number of spaces in the resulting string
+     * @return Padding string of spaces
+     */
+    public static String paddingForWidth(int width) {
+        return " ".repeat(width);
+    }
+
+    private static void appendCharToSb(char c, StringBuilder sb) {
+        if (c == 10) {
+            sb.append("\\n");
+        } else if (c == 13) {
+            sb.append("\\r");
+        } else if (c == 92) {
+            sb.append("\\\\");
+        } else if (c == 34) {
+            sb.append("\\\"");
+        } else if (c < 32 || c > 126) {
+            sb.append("\\u" + String.format("%04X", (int) c));
+        } else {
+            sb.append(c);
+        }
+    }
+}