Micronaut-core: Lombok + @Introspected generates incorrect parameter names

Created on 20 Apr 2019  路  19Comments  路  Source: micronaut-projects/micronaut-core

Summary

The TLDR is that this simple class

@Introspected
@Value
public class Payload {
  private final String id;
}

leads to different parameter names:

// id
Payload.class.getConstructors()[0].getParameters()[0].getName();

// arg0
BeanIntrospection.getIntrospection(Payload.class).getConstructorArguments()[0].getName();

The longer version is that a application.yml:

jackson:
    bean-introspection-module: true

and a controller:

  @Post
  public String create(@Body Payload body) {
    return body.getId();
  }

fails when doing:

POST /bad

{
  "id": "123"
}

with a stack trace like:

io.micronaut.http.client.exceptions.HttpClientResponseException: Failed to convert argument [body] for value [null] due to: Cannot construct instance of `micronaut.introspection.issue.Payload`, problem: Null argument specified for [arg0]. If this argument is allowed to be null annotate it with @Nullable
     at [Source: UNKNOWN; line: -1, column: -1]

It fails in both gradle and maven.

Strangely, it works in eclipse.

Steps to Reproduce

  1. git clone https://github.com/achaphiv/micronaut-introspection-issue
  2. ./gradlew test -i or ./mvnw test

Expected Behaviour

Tests should pass.

Actual Behaviour

Tests fail.

Environment Information

  • Operating System: Mac & Linux
  • Micronaut Version: 1.1.0
  • JDK Version: 1.8.0_202 & 1.8.0_191

Example Application

https://github.com/achaphiv/micronaut-introspection-issue

awaiting third-party

Most helpful comment

Confirmed fixed with (finally released) lombok v1.18.16.

All 19 comments

Pretty sure this is an issue with Lombok. When we are reading the class the parameter name is not there

We can only process what Lombok gives us. This seems to be a Lombok issue as it @Value is not translating id into a named constructor parameter. Probably worth a report to https://github.com/rzwitserloot/lombok/issues

Possibly the problem is that both annotation processors are in the same round. Together with MapStruct, we've been working on a way to have multiple annotation processors that have to run in a specific order, including to run in a next round. Could this be the case here as well?

Alternatively, did someone try to generate a java file using an annotation processor that contains the @Introspected annotation? I see that the example project passes the -parameters argument, but I don't know if javac itself also passes that information to annotation processors for files that are generated.

@rspilker MapStruct currently works with Micronaut, we had some issues with earlier versions, but they seem to be resolved now.

We currently write out bean definitions when processing is over https://github.com/micronaut-projects/micronaut-core/blob/master/inject-java/src/main/java/io/micronaut/annotation/processing/BeanDefinitionInjectProcessor.java#L219

However, in this case the issues appears to be that lombok is creating a constructor in the AST that is not representing the constructor arguments as the field name. The name of the constructor argument should be id instead it is arg0

How does lombok mutate the AST to produce this new node? When it does so does it use the field name to create the VariableElement that represents the constructor argument that is processed later downstream by Micronaut?

I have this issue too. First I though that this could be solved with java.beans.ConstructorProperties that Lombok can generate on constructor. Micronaut currently does not support reading that annotation, but it could. Although after some hacking Micronaut code I figured that annotations generated by Lombok are also not visible from on constructor.

@rspilker do you have any hint how this could be solved for Micronaut?

It seems that this is a Micronaut's issue. I hacked Micronaut code a bit and enabled IntrospectedTypeElementVisitor only when roundEnv.processingOver(). Code change can be seen here: https://github.com/asodja/micronaut-core/commit/9636b770756a836f6950e2258b862dcc38736db6.

If you run LombokIntrospectedClass you can see in build directory that $LombokIntrospectedClass$Introspection.getConstructorArguments() has constructor argument names correctly populated (also annotation java.beans.ConstructorProperties that is generated by Lombok if enabled can be read from constructor).

Although in this case IntrospectionException: No bean introspection available for type exception is thrown since it seems Bean definition is not written.

Annotation processor order is important. The Lombok processor needs to be declared before the Micronaut one so that it runs first

In case, when you use Lombok's @AllArgsConstructor or @RequiredArgsConstructor and Micronaut's @Introspected there is an issue even if you have processors ordered correctly. Micronaut finds created constructor, but when doing Bean Introspection constructor argument names are: arg0, arg1 etc.

Simple reproducer: https://github.com/asodja/micronaut-lombok-reproducer.

I think MapStruct has similar issues and has special interface that is implemented by Lombok:
https://github.com/mapstruct/mapstruct/blob/12bfff8f46c00b22c384f9441d2928b2e588406d/processor/src/main/java/org/mapstruct/ap/spi/AstModifyingAnnotationProcessor.java. With that it can wait a round before visiting the type that is handled by Lombok.

I have similar issue with adding @Named("xyz") to a field and adding lombok.copyableAnnotations += javax.inject.Named in lombok.config.

I can see the disassembled file is correctly generated by Lombok by Micronaut's classes are missing the annotation and fields with lost names.

It would be nice to have it working.

I did some investigation and it looks like a new constructor generated in Lombok doesn't have parameter names and annotations but if the processing is deferred to the next round they are present.

@rspilker Any idea why is this happening? Doesn't look like it's Micronaut related, the processing order is correct otherwise there wouldn't be a constructor present.

@graemerocher I have a possible solution to defer introspected processing for classes with Lombok https://github.com/dstepanov/micronaut-core/commit/27f71c791a778b0e51381672195d68b108f543c7

Interesting, seems a bit hacky, would be interested to hear @rspilker comments on this

Maybe it's worth reading: https://github.com/mapstruct/mapstruct/issues/510#issuecomment-164204056 (and other comments in this issue). It seems like similar issue.

Tl;dr of how they solved it can be found here: https://github.com/rzwitserloot/lombok/issues/973#issuecomment-342289891

Found the problem to be in Lombok https://github.com/rzwitserloot/lombok/pull/2497

Thanks for the looking into that @dstepanov I don't personally think we should place hacks into Micronaut to workaround issues in Lombok so would greatly prefer this to be fixed upstream

This was fixed upstream

Sorry to post to a closed Issue, but I discovered an unexpected behaviour that might of interest. I posted my findings in the lombok issue https://github.com/rzwitserloot/lombok/pull/2497

Note: I do not use @Introspected but @MappedEntity instead so it might not be the exact same issue.

@altery Your latest post https://github.com/micronaut-projects/micronaut-core/issues/1566#issuecomment-666485008 links back to this very post.
Endless loop :scream:

Thanks for noticing, @SchulteMarkus , the link is now corrected 馃檲

Confirmed fixed with (finally released) lombok v1.18.16.

Was this page helpful?
0 / 5 - 0 ratings