Search code examples
spring-bootmavenlombokgraphql-java

mvn spring-boot:run "No primary or single public constructor found...TemplateLineItemInput" but only from commandline?


I've got an up-the-middle Spring Boot + Lombok project that works like a champ from the IDE but errors strangely when I run it from the command line through mvn spring-boot:run. Its a pretty recent version Spring Boot...

    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.5.4</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>

Lombok is unremarkable and came from the Spring Initializr thing (https://start.spring.io/).

        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>

The JavaBean its complaining about is equally boring...

import lombok.Builder;
import lombok.Data;

import java.math.BigDecimal;

@Data
@Builder(toBuilder = true)
public class TemplateLineItemInput {
  private final String partMasterId;
  private final String partMasterIdPath;
  private final String actionType;
  private final BigDecimal actionQuantity;
}

The API of this Boot project is GraphQL but when I execute the following mutation from a mvn spring-boot:run invocation it always comes back as an error (nothing on the console...the framework is kicking it out somehow).

Request...

mutation createTemplateLineItem($tbomId: ID!) {
  createTemplateLineItem(
    tbomId: $tbomId
    input: {
      partMasterId: "2"
      partMasterIdPath: "808863036.1"
      actionType: "ADD"
      actionQuantity: "2"
    }) {
    ...TBomFrag
  }
}
...
{
  "partMasterId": "5025489768",
  "tbomId": "a4688d22-9d99-41a2-9777-6acc75b2aab9",
  "lineItemId": "9e460199-34fb-432c-b971-8cd8321d3283"
}

Response...
{
  "errors": [
    {
      "message": "No primary or single public constructor found for class aero.blue.ems.boa.domain.TemplateLineItemInput - and no default constructor found either",
      "locations": [
        {
          "line": 20,
          "column": 3
        }
      ],
      "path": [
        "createTemplateLineItem"
      ],
      "extensions": {
        "classification": "INTERNAL_ERROR"
      }
    }
  ],
  "data": {
    "createTemplateLineItem": null
  }
}

My spring-boot-maven-plugin is configured like...

            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
                <version>2.5.4</version>
                <configuration>
                    <excludes>
                        <exclude>
                            <groupId>org.projectlombok</groupId>
                            <artifactId>lombok</artifactId>
                        </exclude>
                    </excludes>
                </configuration>
                <executions>
                    <execution>
                        <id>repackage</id>
                        <configuration>
                            <classifier>exec</classifier>
                        </configuration>
                    </execution>
                    <execution>
                        <goals>
                            <goal>build-info</goal>
                        </goals>
                    </execution>
                </executions>
            </plugin>

...also from Spring Initializr

When I run the Application from the IDE directly, no issue. The mutation works fine.

Is there something missing from my spring-boot:run config in the pom.xml or something? Did I have to clue the plugin into annotation processing? This is really confusing.


Solution

  • Ultimately this comes down to Lombok misconfiguration of my beans. The fix is to add the @AllArgsConstructor to the bean definition

    import lombok.AllArgsConstructor;
    import lombok.Builder;
    import lombok.Data;
    
    import java.math.BigDecimal;
    
    @Data
    @Builder(toBuilder = true)
    @AllArgsConstructor
    public class TemplateLineItemInput {
      private final String partMasterId;
      private final String partMasterIdPath;
      private final String actionType;
      private final BigDecimal actionQuantity;
    }
    
    

    How we figured this out was to "Delombok" the Bean and look at the resulting code. This observation matched the error message; there was no public constructor.

      ...
      TemplateLineItemInput(String partMasterId, String partMasterIdPath, String actionType, BigDecimal actionQuantity) {
        this.partMasterId = partMasterId;
        this.partMasterIdPath = partMasterIdPath;
        this.actionType = actionType;
        this.actionQuantity = actionQuantity;
      }
    
    

    Somehow (I still don't fully get why), the @Builder(toBuilder=true) annotation had Lombok producing a package private constructor. Jackson needed something public.

    Adding the @AllArgsConstructor annotation made that constructor public and all is well.

      public TemplateLineItemInput(String partMasterId, String partMasterIdPath, String actionType, BigDecimal actionQuantity) {
        this.partMasterId = partMasterId;
        this.partMasterIdPath = partMasterIdPath;
        this.actionType = actionType;
        this.actionQuantity = actionQuantity;
      }
    

    Delombok was the key.