Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

java 17, the ReflectionHelpers.setStaticField does not work on a final field

Android project's unit test, which needs to test with different BuildConfig.DEBUG value

The BuildConfig class has DEBUG as final

public final class BuildConfig {
  public static final boolean DEBUG = Boolean.parseBoolean("true");
}

This code works fine until update to java 17

ReflectionHelpers.setStaticField(BuildConfig.class, "DEBUG", true);

With java 17, now it gets error:

IllegalArgumentException: Cannot set the value of final field public static final boolean com.oath.mobile.shadowfax.adm.BuildConfig.DEBUG
java.lang.RuntimeException: java.lang.RuntimeException: 

I think for the test case we can modify the code to use a function for the BuildConfig.DEBUG value. But ingeneral is there alternative to do modify a final field of a class?

like image 763
lannyf Avatar asked Aug 13 '26 17:08

lannyf


1 Answers

As another has pointed out, there is this great answer. But the relevant part that should work specifically for you is through using the java.lang.invoke package to modify the DEBUG field in your test environment. I saw you mention this is for tests, so should be fair game. But I wouldn't use this in production.

Also note this likely will not work for Java 18+

import java.lang.invoke.MethodHandles;
import java.lang.invoke.VarHandle;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;

public class TestBuildConfig {

  private static final VarHandle MODIFIERS;

  static {
    try {
      var lookup = MethodHandles.privateLookupIn(Field.class, MethodHandles.lookup());
      MODIFIERS = lookup.findVarHandle(Field.class, "modifiers", int.class);
    } catch (IllegalAccessException | NoSuchFieldException ex) {
      throw new RuntimeException(ex);
    }
  }

  public static void main(String[] args) throws Exception {
    var debugField = BuildConfig.class.getDeclaredField("DEBUG");
    // make field non-final
    MODIFIERS.set(debugField, debugField.getModifiers() & ~Modifier.FINAL);
    
    // set field to new value
    debugField.setAccessible(true);
    debugField.set(null, false);  // Change value as needed

    // Conduct tests
    System.out.println("DEBUG: " + BuildConfig.DEBUG);
  }
}

Compile and run with:

javac TestBuildConfig.java
java --add-opens=java.base/java.lang.reflect=ALL-UNNAMED TestBuildConfig

This allows testing different BuildConfig.DEBUG values without changing the original source code.

like image 126
Kyle Venn Avatar answered Aug 19 '26 23:08

Kyle Venn



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!