Skip to content

JacksonBlackbirdAccess Class access exception with Groovy Script Serialization using BlackbirdModule #285

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
apoorvam opened this issue Feb 17, 2025 · 14 comments
Labels
blackbird Issue related to Blackbird module

Comments

@apoorvam
Copy link
Contributor

apoorvam commented Feb 17, 2025

I'm encountering an issue when attempting to use the BlackbirdModule in a Spring Boot 3.2 application with Groovy scripting. The goal is to serialize a Groovy object into JSON using the Jackson ObjectMapper with the BlackbirdModule. However, the test case below fails with an exception during the serialization of a Groovy object.

 @Test
    void testGroovySerializationWithBlackbird() throws Exception {
        // Define a Groovy script as a String
        String script = """
                    import com.fasterxml.jackson.databind.ObjectMapper
                    import com.fasterxml.jackson.module.blackbird.BlackbirdModule

                    class Person {
                        String name
                        int age
                    }

                    def mapper = new ObjectMapper()
                    mapper.registerModule(new BlackbirdModule())

                    def person = new Person(name: "Groovy User", age: 35)
                    def json = mapper.writeValueAsString(person)

                    return json // Returning the JSON string
                """;

        // Execute the Groovy script
        GroovyShell shell = new GroovyShell();
        Object result = shell.evaluate(script);

        assertNotNull(result);
    }

Exception:

java.lang.ClassFormatError: Illegal class name "/$$JacksonBlackbirdAccess" in class file /$$JacksonBlackbirdAccess
	at java.base/java.lang.ClassLoader.defineClass0(Native Method)
	at java.base/java.lang.System$2.defineClass(System.java:2394)
	at java.base/java.lang.invoke.MethodHandles$Lookup$ClassDefiner.defineClass(MethodHandles.java:2505)
	at java.base/java.lang.invoke.MethodHandles$Lookup$ClassDefiner.defineClass(MethodHandles.java:2480)
	at java.base/java.lang.invoke.MethodHandles$Lookup.defineClass(MethodHandles.java:1865)
	at com.fasterxml.jackson.module.blackbird.CrossLoaderAccess.accessClassIn(CrossLoaderAccess.java:131)
	at com.fasterxml.jackson.module.blackbird.CrossLoaderAccess.grantAccess(CrossLoaderAccess.java:94)
	at com.fasterxml.jackson.module.blackbird.CrossLoaderAccess.apply(CrossLoaderAccess.java:83)
	at com.fasterxml.jackson.module.blackbird.CrossLoaderAccess.apply(CrossLoaderAccess.java:11)
	at com.fasterxml.jackson.module.blackbird.ser.BBSerializerModifier.createProperty(BBSerializerModifier.java:109)
	at com.fasterxml.jackson.module.blackbird.ser.BBSerializerModifier.lambda$findProperties$0(BBSerializerModifier.java:67)
	at com.fasterxml.jackson.module.blackbird.util.Unchecked.lambda$runnable$0(Unchecked.java:31)
	at com.fasterxml.jackson.module.blackbird.ser.BBSerializerModifier.findProperties(BBSerializerModifier.java:68)
	at com.fasterxml.jackson.module.blackbird.ser.BBSerializerModifier.changeProperties(BBSerializerModifier.java:52)
	at com.fasterxml.jackson.databind.ser.BeanSerializerFactory.constructBeanOrAddOnSerializer(BeanSerializerFactory.java:415)
	at com.fasterxml.jackson.databind.ser.BeanSerializerFactory.findBeanOrAddOnSerializer(BeanSerializerFactory.java:295)
	at com.fasterxml.jackson.databind.ser.BeanSerializerFactory._createSerializer2(BeanSerializerFactory.java:240)
	at com.fasterxml.jackson.databind.ser.BeanSerializerFactory.createSerializer(BeanSerializerFactory.java:174)
	at com.fasterxml.jackson.databind.SerializerProvider._createUntypedSerializer(SerializerProvider.java:1503)
	at com.fasterxml.jackson.databind.SerializerProvider._createAndCacheUntypedSerializer(SerializerProvider.java:1451)
	at com.fasterxml.jackson.databind.SerializerProvider.findValueSerializer(SerializerProvider.java:556)
	at com.fasterxml.jackson.databind.SerializerProvider.findTypedValueSerializer(SerializerProvider.java:834)
	at com.fasterxml.jackson.databind.ser.DefaultSerializerProvider.serializeValue(DefaultSerializerProvider.java:307)
	at com.fasterxml.jackson.databind.ObjectMapper._writeValueAndClose(ObjectMapper.java:4719)
	at com.fasterxml.jackson.databind.ObjectMapper.writeValueAsString(ObjectMapper.java:3964)
	at org.codehaus.groovy.vmplugin.v8.IndyInterface.fromCache(IndyInterface.java:321)
	at Script1.run(Script1.groovy:13)
	at groovy.lang.GroovyShell.evaluate(GroovyShell.java:460)
	at groovy.lang.GroovyShell.evaluate(GroovyShell.java:495)
	at groovy.lang.GroovyShell.evaluate(GroovyShell.java:469)
	at JacksonBlackbirdTest.testGroovySerializationWithBlackbird(JacksonBlackbirdTest.java:100)
	at java.base/java.lang.reflect.Method.invoke(Method.java:580)
	at java.base/java.util.ArrayList.forEach(ArrayList.java:1596)
	at java.base/java.util.ArrayList.forEach(ArrayList.java:1596)

I am using jackson-bom version 2.15.4 and Groovy 4.0.17 that's default with Spring Boot 3.2. Any suggestions? Removing Blackbird module in above test passes, works even with afterburner module. Just not with blackbird module.

@apoorvam
Copy link
Contributor Author

Hi, would like to know if there are any workarounds for this. Any suggestions?

@cowtowncoder cowtowncoder added the blackbird Issue related to Blackbird module label Feb 25, 2025
@cowtowncoder
Copy link
Member

My main suggestion would be to try a more recent version -- ideally 2.18.2 -- of Jackson components.
There are occasionally fixes to Blackbird and this would rule out possibility later version works better.

Otherwise I suspect Groovy-generated class has name that somehow Blackbird has issues with. Perhaps it is in the "default package" (i.e. one without name) and BB does not handle that case well.

@stevenschlansker WDYT?

@apoorvam
Copy link
Contributor Author

apoorvam commented Feb 25, 2025

I tried with 2.18.2 and see the same error unfortunately.

java.lang.ClassFormatError: Illegal class name "/$$JacksonBlackbirdAccess" in class file /$$JacksonBlackbirdAccess
	at java.base/java.lang.ClassLoader.defineClass0(Native Method)
	at java.base/java.lang.System$2.defineClass(System.java:2394)
	at java.base/java.lang.invoke.MethodHandles$Lookup$ClassDefiner.defineClass(MethodHandles.java:2505)
	at java.base/java.lang.invoke.MethodHandles$Lookup$ClassDefiner.defineClass(MethodHandles.java:2480)
	at java.base/java.lang.invoke.MethodHandles$Lookup.defineClass(MethodHandles.java:1865)
	at com.fasterxml.jackson.module.blackbird.CrossLoaderAccess.accessClassIn(CrossLoaderAccess.java:131)
	at com.fasterxml.jackson.module.blackbird.CrossLoaderAccess.grantAccess(CrossLoaderAccess.java:94)
	at com.fasterxml.jackson.module.blackbird.CrossLoaderAccess.apply(CrossLoaderAccess.java:83)
	at com.fasterxml.jackson.module.blackbird.CrossLoaderAccess.apply(CrossLoaderAccess.java:11)
	at com.fasterxml.jackson.module.blackbird.ser.BBSerializerModifier.createProperty(BBSerializerModifier.java:111)
	at com.fasterxml.jackson.module.blackbird.ser.BBSerializerModifier.lambda$findProperties$0(BBSerializerModifier.java:69)
	at com.fasterxml.jackson.module.blackbird.util.Unchecked.lambda$runnable$0(Unchecked.java:31)
	at com.fasterxml.jackson.module.blackbird.ser.BBSerializerModifier.findProperties(BBSerializerModifier.java:70)
	at com.fasterxml.jackson.module.blackbird.ser.BBSerializerModifier.changeProperties(BBSerializerModifier.java:54)
	at com.fasterxml.jackson.databind.ser.BeanSerializerFactory.constructBeanOrAddOnSerializer(BeanSerializerFactory.java:415)
	at com.fasterxml.jackson.databind.ser.BeanSerializerFactory.findBeanOrAddOnSerializer(BeanSerializerFactory.java:295)
	at com.fasterxml.jackson.databind.ser.BeanSerializerFactory._createSerializer2(BeanSerializerFactory.java:240)
	at com.fasterxml.jackson.databind.ser.BeanSerializerFactory.createSerializer(BeanSerializerFactory.java:174)
	at com.fasterxml.jackson.databind.SerializerProvider._createUntypedSerializer(SerializerProvider.java:1523)
	at com.fasterxml.jackson.databind.SerializerProvider._createAndCacheUntypedSerializer(SerializerProvider.java:1471)
	at com.fasterxml.jackson.databind.SerializerProvider.findValueSerializer(SerializerProvider.java:578)
	at com.fasterxml.jackson.databind.SerializerProvider.findTypedValueSerializer(SerializerProvider.java:856)
	at com.fasterxml.jackson.databind.ser.DefaultSerializerProvider.serializeValue(DefaultSerializerProvider.java:330)
	at com.fasterxml.jackson.databind.ObjectMapper._writeValueAndClose(ObjectMapper.java:4811)
	at com.fasterxml.jackson.databind.ObjectMapper.writeValueAsString(ObjectMapper.java:4052)
	at org.codehaus.groovy.vmplugin.v8.IndyInterface.fromCache(IndyInterface.java:321)
	at Script1.run(Script1.groovy:13)
	at groovy.lang.GroovyShell.evaluate(GroovyShell.java:460)
	at groovy.lang.GroovyShell.evaluate(GroovyShell.java:495)
	at groovy.lang.GroovyShell.evaluate(GroovyShell.java:469)

@cowtowncoder How can I inspect this Groovy generated class to root cause this?

@cowtowncoder
Copy link
Member

To be honest, I don't know. Maybe debugging on com.fasterxml.jackson.module.blackbird.CrossLoaderAccess.accessClassIn would show name of dynamic class being generated?

@apoorvam
Copy link
Contributor Author

apoorvam commented Mar 6, 2025

@cowtowncoder Debugging accessClassIn method,

accessClassName = ".$$JacksonBlackbirdAccess"
fqcn = "/$$JacksonBlackbirdAccess"
and finally throws a ClassFormatError: java.lang.ClassFormatError: Illegal class name "/$$JacksonBlackbirdAccess" in class file /$$JacksonBlackbirdAccess at line:
return (Class<?>) DEFINE_CLASS.invokeExact(lookup, classBytes.toByteArray());

What made my test pass is adding a package name at beginning of script

    @Test
    void testGroovySerializationWithBlackbird() throws Exception {
        // Define a Groovy script as a String
        String script = """
                    package abc;
                    import com.fasterxml.jackson.databind.ObjectMapper
                    import com.fasterxml.jackson.module.blackbird.BlackbirdModule

                    class Person {
                        String name
                        int age
                    }

                    def mapper = new ObjectMapper()
                    mapper.registerModule(new BlackbirdModule())

                    def person = new Person(name: "Groovy User", age: 35)
                    def json = mapper.writeValueAsString(person)

                    return json // Returning the JSON string
                """;

        // Execute the Groovy script
        GroovyShell shell = new GroovyShell();
        Object result = shell.evaluate(script);

        assertNotNull(result);
    }

While i couldn't test this exactly, i think handling case where pkg.getName() is empty in accessClassIn method would fix this.

            String fqcn = pkg.getName()
                    .replace('.', '/')
                + "/" + CLASS_NAME;

to

String fqcn = (pkg.getName().isEmpty() ? "" : pkg.getName().replace('.', '/') + "/") + CLASS_NAME;

@cowtowncoder
Copy link
Member

@apoorvam Thank you for sharing! This should help resolve the issue -- main challenge is probably just adding a test to verify (ideally not adding Groovy dependency even just for tests). Might work with just root-level test class -- or maybe using JDK script support.

That is: I think your fix would probably work, but we'd need a verification of some kind to prove it (and guard against regression).

@apoorvam
Copy link
Contributor Author

apoorvam commented Mar 6, 2025

    public static class Dummy {
    }

    @Test
    public void testAccessClassInDefaultPackage() throws Exception {
        MethodHandles.Lookup lookup = MethodHandles.lookup().in(Dummy.class);
        
        Class<?> accessClass = CrossLoaderAccess.accessClassIn(lookup);
        
        assertNotNull(accessClass);
        assertEquals("$$JacksonBlackbirdAccess", accessClass.getSimpleName());
    }

@cowtowncoder Something like this? I'll have to test it though. I couldn't test it because CrossLoaderAccess class isn't exposed externally

@cowtowncoder
Copy link
Member

I was thinking more an actual end-to-end test of serializing/deserializing root-level value class. Sort of reproducing your actual use case wrt value class not being in any package.

apoorvam added a commit to apoorvam/jackson-modules-base that referenced this issue Mar 7, 2025
@apoorvam
Copy link
Contributor Author

apoorvam commented Mar 7, 2025

Is there a CI pipeline that i can run my test on? #289 @cowtowncoder

@stevenschlansker
Copy link
Contributor

Thanks for tracking this down, I did not consider the default package when writing this, I agree that is likely the problem.

@cowtowncoder
Copy link
Member

@apoorvam I had to approve running of CI since you're new contributor.

@cowtowncoder cowtowncoder changed the title JacksonBlackbirdAccess Class access exception with Groovy Script Serialization Using BlackbirdModule JacksonBlackbirdAccess Class access exception with Groovy Script Serialization using BlackbirdModule Mar 8, 2025
cowtowncoder pushed a commit that referenced this issue Mar 8, 2025
apoorvam pushed a commit to apoorvam/jackson-modules-base that referenced this issue Mar 8, 2025
@apoorvam
Copy link
Contributor Author

apoorvam commented Mar 8, 2025

@cowtowncoder When will 2.19 be released? Also, amended a change: #292

@cowtowncoder
Copy link
Member

@apoorvam With all the work for 3.0.0-rc1 (just released), it may take a while. No firm plans.

Then again it might be time to consider 2.19.0-rc1 to get 2.19.0 out of the way.

So... I don't know :)

Thanks for follow up wrt #292.

cowtowncoder pushed a commit that referenced this issue Mar 8, 2025
@apoorvam
Copy link
Contributor Author

Looking forward for v2.19.0 with this fix! Thank you.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
blackbird Issue related to Blackbird module
Projects
None yet
Development

No branches or pull requests

3 participants