Skip to content

[Java] DefaultValueUtils warns on Lombok @Builder.Default classes when Scala is on classpath #3720

@mandrean

Description

@mandrean

Search before asking

  • I had searched in the issues and found no similar issues.

Version

Apache Fory 1.0.0, Java serializer, CompatibleMode.COMPATIBLE.

The warning is seen in a Java application when scala.Product is loadable from the runtime classpath. The application code and affected serialized classes are Java classes, not Scala classes.

It is enough for the application to depend on some ordinary Java-facing library that happens to pull in Scala transitively. The application does not need to declare org.scala-lang:scala-library directly and does not need to use any Scala APIs.

Component(s)

Java

Minimal reproduce step

Create a small Maven project with Fory 1.0.0, Lombok, and any Java-facing dependency that brings scala-library onto the runtime classpath transitively. The Java code does not need to use Scala; scala-library only needs to be present transitively so org.apache.fory.type.ScalaTypes.SCALA_AVAILABLE becomes true.

pom.xml:

<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
  <modelVersion>4.0.0</modelVersion>

  <groupId>repro</groupId>
  <artifactId>fory-lombok-default-repro</artifactId>
  <version>1.0-SNAPSHOT</version>

  <properties>
    <maven.compiler.release>17</maven.compiler.release>
  </properties>

  <dependencies>
    <dependency>
      <groupId>org.apache.fory</groupId>
      <artifactId>fory-core</artifactId>
      <version>1.0.0</version>
    </dependency>
    <dependency>
      <groupId>org.projectlombok</groupId>
      <artifactId>lombok</artifactId>
      <version>1.18.36</version>
      <scope>provided</scope>
    </dependency>
    <!-- Used only as an example of a Java application dependency that brings scala-library transitively. -->
    <dependency>
      <groupId>org.apache.kafka</groupId>
      <artifactId>kafka_2.13</artifactId>
      <version>3.6.2</version>
    </dependency>
    <dependency>
      <groupId>org.slf4j</groupId>
      <artifactId>slf4j-simple</artifactId>
      <version>2.0.12</version>
    </dependency>
  </dependencies>

  <build>
    <plugins>
      <plugin>
        <groupId>org.codehaus.mojo</groupId>
        <artifactId>exec-maven-plugin</artifactId>
        <version>3.3.0</version>
      </plugin>
    </plugins>
  </build>
</project>

src/main/java/repro/LombokDefaultRepro.java:

package repro;

import java.util.List;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Value;
import org.apache.fory.Fory;
import org.apache.fory.config.CompatibleMode;
import org.apache.fory.config.Language;
import org.apache.fory.logging.LoggerFactory;

public class LombokDefaultRepro {
    @Value
    @Builder
    @AllArgsConstructor
    public static class JavaPojo {
        String id;

        @Builder.Default
        List<String> imageRelations = List.of();
    }

    public static void main(String[] args) {
        LoggerFactory.useSlf4jLogging(true);

        Fory fory = Fory.builder()
                .withLanguage(Language.JAVA)
                .requireClassRegistration(false)
                .withCompatibleMode(CompatibleMode.COMPATIBLE)
                .build();

        byte[] bytes = fory.serialize(JavaPojo.builder().id("1").build());
        fory.deserialize(bytes, JavaPojo.class);
    }
}

Run:

mvn -q compile exec:java -Dexec.mainClass=repro.LombokDefaultRepro

Confirm that Scala is present transitively:

mvn -q dependency:tree -Dincludes=org.scala-lang:scala-library

Expected dependency path:

repro:fory-lombok-default-repro:jar:1.0-SNAPSHOT
\- org.apache.kafka:kafka_2.13:jar:3.6.2:compile
   \- org.scala-lang:scala-library:jar:2.13.11:compile

Lombok compiles @Builder.Default into a private static method whose name has the shape $default$imageRelations():

javap -classpath target/classes -private 'repro.LombokDefaultRepro$JavaPojo'

Relevant output:

private static java.util.List<java.lang.String> $default$imageRelations();

If the Kafka dependency, or any other dependency that makes scala.Product loadable, is removed, the warning is not emitted because the Scala default-value branch is not enabled.

What did you expect to see?

No warning should be emitted for a plain Java class just because Scala is present somewhere on the runtime classpath.

Fory's Scala default-value support should either only inspect Scala types, or it should only treat method names as Scala default methods when the suffix after $default$ is numeric, such as apply$default$1.

What did you see instead?

During deserialization, Fory logs a warning from DefaultValueUtils:

WARN org.apache.fory.util.DefaultValueUtils - Error For input string: "imageRelations" finding default value for repro.LombokDefaultRepro$JavaPojo, default values support is disabled when deserializing object of type repro.LombokDefaultRepro$JavaPojo

This happens even though JavaPojo is a Java class and the repro does not call any Kafka or Scala APIs. The only Scala-related condition is that scala.Product is loadable from the classpath via a transitive dependency.

Anything Else?

The suspected path is:

  • CompatibleSerializer enables Scala default-value support when ScalaTypes.SCALA_AVAILABLE is true.
  • ScalaTypes.SCALA_AVAILABLE is global and becomes true when scala.Product can be loaded.
  • DefaultValueUtils.ScalaDefaultValueSupport#getDefaultValuesForRegularScalaClass scans all declared methods whose names contain $default$.
  • It then parses the substring after $default$ as an integer.
  • Lombok @Builder.Default generates methods like $default$imageRelations, where the suffix is a Java field name, not a Scala argument index.

This makes a transitive Scala dependency enough to produce warning noise for Java Lombok model classes. In practice this can happen just by adding a Java-facing library whose own dependency graph includes Scala, even if the application is otherwise pure Java.

Are you willing to submit a PR?

  • I'm willing to submit a PR!

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions