Can I change constant from int to byte in Java without breaking backward compatibility?

Maybe it's obvious. But maybe not. If some third party user fetched the int values and stored them locally in Integer objects, changing the int to byte will cause a compile time error there:

interface IntToByteWithInt
{
    int VER_EQUAL = 1;
    int VER_GREATER = 2;
    int VER_GREATER_EQUAL = 3;
}

interface IntToByteWithByte
{
    byte VER_EQUAL = 1;
    byte VER_GREATER = 2;
    byte VER_GREATER_EQUAL = 3;
}

public class IntToByte
{
    public static void main(String[] args)
    {
        Integer a = IntToByteWithInt.VER_EQUAL;

        // Type mismatch: cannot convert from byte to Integer  
        Integer b = IntToByteWithByte.VER_EQUAL;
    }
}

Beyond that, I think that one should at least mention reflection for completeness.

I also considered the ternary operator (https://docs.oracle.com/javase/specs/jls/se8/html/jls-15.html#jls-15.25.2 ) as a hot candidate, but did not manage to cause an error with that one.


Is this change backwards compatible?

No.

Even though constants are normally inlined, the field is still a part of the class file, so it is possible that some dynamic lookup references the field using it's old type which is int. For instance:

// defined in `MyClass`
static final byte x = 10;

public static void main(String[] args) throws Throwable {
    lookup().findStaticGetter(MyClass.class, "x", int.class); // old code
}

This throws a NoSuchFieldException since the lookup is looking for the old type of the field.

This also goes for byte code writing APIs that access the field, e.g. with ASM:

ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_MAXS);
cw.visit(55, ACC_PUBLIC, "Test", null, "java/lang/Object", null);

MethodVisitor mv = cw.visitMethod(ACC_PUBLIC | ACC_STATIC, "m", "()V", null, null);
mv.visitCode();
mv.visitFieldInsn(GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;");
mv.visitFieldInsn(GETSTATIC, "MyClass", "x", "I"); // looking for int field
mv.visitMethodInsn(INVOKEVIRTUAL, "java/io/PrintStream", "println", "(I)V", false);
mv.visitInsn(RETURN);
mv.visitMaxs(-1, -1);
mv.visitEnd();

Class<?> cls = lookup().defineClass(cw.toByteArray());
cls.getMethod("m").invoke(null); // NoSuchFieldError

The above code works and prints 10 if the field is of type int, but fails once it is changed to byte.