The actual reason
The output varies and is sometimes 0, sometimes not as you already noticed. The likeliest issue is that System.nanoTime:
provides nanosecond precision, but not necessarily nanosecond resolution (that is, how frequently the value changes)
So 0 does not necessarily mean 0 - it just means: less than the smallest "tick" that your clock can represent.
Can we be sure that the code hasn't been optimised by the compiler?
If you use the -XX:+PrintCompilation flag when running the program, you will get an output similar to below. If you read carefully, you will notice that main has not been compiled by the JIT yet (which makes sense since it is not really hot) - so your code can't have been optimised away.
62 1 3 java.lang.String::hashCode (55 bytes) 62 3 3 java.lang.String::indexOf (70 bytes) 63 2 3 java.lang.String::charAt (29 bytes) 65 4 3 java.lang.AbstractStringBuilder::ensureCapacityInternal (16 bytes) 65 5 3 java.lang.Object::<init> (1 bytes) 65 8 n 0 java.lang.System::arraycopy (native) (static) 65 6 3 java.lang.CharacterData::of (120 bytes) 66 7 3 java.lang.CharacterDataLatin1::getProperties (11 bytes) 66 9 3 java.lang.Character::toLowerCase (9 bytes) 66 10 3 java.lang.CharacterDataLatin1::toLowerCase (39 bytes) 66 11 3 java.lang.String::length (6 bytes) 66 12 3 java.lang.AbstractStringBuilder::append (29 bytes) 66 13 3 java.lang.StringBuilder::append (8 bytes) 67 14 3 java.io.WinNTFileSystem::isSlash (18 bytes) 67 15 s 3 java.lang.StringBuffer::append (13 bytes) 67 16 3 java.lang.String::<init> (62 bytes) 68 17 3 java.lang.String::equals (81 bytes) 68 18 1 java.lang.Object::<init> (1 bytes) 68 5 3 java.lang.Object::<init> (1 bytes) made not entrant 68 19 3 java.lang.String::getChars (62 bytes) 74 20 3 java.lang.Math::min (11 bytes) 74 21 3 java.io.BufferedInputStream::getBufIfOpen (21 bytes) 74 22 3 java.util.Arrays::copyOfRange (63 bytes) 75 24 3 java.io.DataInputStream::readUTF (501 bytes) 76 29 3 java.io.DataInputStream::readFully (63 bytes) 77 26 s 3 java.io.BufferedInputStream::read (113 bytes) 77 23 s 3 java.io.BufferedInputStream::read (49 bytes) 77 30 3 java.io.DataInputStream::readShort (40 bytes) 78 25 3 java.io.DataInputStream::readUTF (5 bytes) 78 27 3 java.io.BufferedInputStream::read1 (108 bytes) 78 28 3 java.io.DataInputStream::readUnsignedShort (39 bytes) 78 31 3 java.util.HashMap::hash (20 bytes) 79 32 s 3 java.io.ByteArrayInputStream::read (36 bytes) 79 33 3 java.io.DataInputStream::readByte (23 bytes) 79 34 3 sun.util.calendar.ZoneInfoFile::indexOf (32 bytes) 80 35 3 java.util.zip.CRC32::update (16 bytes) 81 36 n 0 java.util.zip.CRC32::updateBytes (native) (static) 81 37 3 sun.util.calendar.ZoneInfoFile$Checksum::update (39 bytes) 83 39 3 java.util.HashMap::putVal (300 bytes) 84 41 4 java.lang.String::equals (81 bytes) 84 38 3 java.util.HashMap::put (13 bytes) 84 42 ! 3 java.io.BufferedReader::readLine (304 bytes) 86 43 3 java.util.LinkedList::indexOf (73 bytes) 86 44 3 java.util.HashMap::getNode (148 bytes) 86 46 3 java.util.HashMap::get (23 bytes) 87 47 3 sun.misc.JarIndex::addToList (59 bytes) 87 17 3 java.lang.String::equals (81 bytes) made not entrant 87 45 3 java.util.HashMap$Node::<init> (26 bytes) 87 40 3 java.util.HashMap::newNode (13 bytes) 88 48 4 java.lang.String::charAt (29 bytes) 88 2 3 java.lang.String::charAt (29 bytes) made not entrant 92 49 3 java.lang.AbstractStringBuilder::append (50 bytes) 92 50 3 java.lang.System::getSecurityManager (4 bytes) 93 51 3 java.lang.Math::max (11 bytes) 93 52 3 java.lang.String::endsWith (17 bytes) 96 53 3 java.lang.StringBuilder::append (8 bytes) equal 189546 equal 17232 367 0 97 54 1 java.nio.Buffer::position (5 bytes)