GV

GeezerVison

The World Wide Web, restored to 800 pixels of glory.

Home|About|Gaming cabinet|Archives|How it works|Starter sites

On this site

  • Sign in
  • ActionsCI/CD and automation
  • CodespacesCloud development environments
  • All Features
  • Documentation
  • Training
  • Certifications
  • Build with Copilot
  • Customize Copilot
  • Integrations
  • Trending repos
  • GitHub Sponsors
  • Developer articles
  • Enterprises

This view

  • Original page
  • New address

GeezerVison is showing github.com — Add annotations to Generated code · Issue #1574 · mapstruct/mapstruct

Add annotations to Generated code #1574

New issue
Copy link
New issue
Copy link
Closed
#2792
Closed
Add annotations to Generated code#1574
#2792
Copy link
Assignees
Zegveld
Labels
feature
Milestone
1.6.0.Beta1

Description

@vadim-shb
vadim-shb
opened on Aug 5, 2018
Issue body actions

I just want to share an idea... I think it would be great to be able to add any annotations you want to generated classes/methods.
Something like this:

@Annotated({
  @Component,
  @Qualifier("fooMapper")
})
@Mapper
public interface FooMapper {
  @Annotated({
    @Cacheable(value = "cache", key = "mappedFoo #foo.id" )
  })
  Foo map(Foo foo);
}

So, the content of @Annotated would be added to generated code like this:

@Component,
@Qualifier("fooMapper")
public class FooMapperImpl implements FooMapper {
  @Cacheable(value = "cache", key = "mappedFoo #foo.id" )
  Foo map(Foo foo) {
  ...
  }
}

With this functionality we wouldn't need @Mapper(componentModel = "spring") for example, and many annotation-based issues would gone.

Reacted by xqdd, Martin Weise, Michael Brem, Falko Modler, Michael Kroll, Marco Martins, payne911, Sean Flanigan, Ivan Vakhrushev, LukeDunsmore and 4 more

Activity

Next
  1. sjaakd commented on Aug 5, 2018

    @sjaakd
    sjaakd
    on Aug 5, 2018
    Contributor
    More actions

    Interesting. I'm not an expert on Spring but to my understanding some of the annotations end up on a referenced mapper instance. Like '@Inject' for CDI. How would that work with your idea?

  2. vadim-shb commented on Aug 5, 2018

    @vadim-shb
    vadim-shb
    on Aug 5, 2018
    Author
    More actions

    I'm not sure if I've understood your question correctly, so please rephrase it then, or add an example...

    In context of Spring @Inject would work just fine with the example above (if package with FooMapperImpl.java is scanned for beans by Spring):

    @Component
    public class SomeBean {
      @Inject
      @Qualifier("fooMapper")
      FooMapper mapper
    
      ...
    }
    
  3. sjaakd commented on Aug 5, 2018

    @sjaakd
    sjaakd
    on Aug 5, 2018
    Contributor
    More actions

    When you use a mapper, ('@Mapper#use') MapStruct will generate that code when the component model is Spring. Don't know if it also evaluates the annotations on the used mapper

  4. vadim-shb commented on Aug 5, 2018

    @vadim-shb
    vadim-shb
    on Aug 5, 2018
    Author
    More actions

    Ok, I've never used @Mapper#use, and after quick peek I still not see where I would like to use it, but it was probably a mistake talking that

    we wouldn't need @Mapper(componentModel = "spring")

    But it still might be useful in different situations... Leave it here to discuss...

  5. filiphr commented on Aug 5, 2018

    @filiphr
    filiphr
    on Aug 5, 2018
    Member
    More actions

    @vadim-shb the request is interesting.

    Regarding:

    With this functionality we wouldn't need @Mapper(componentModel = "spring") for example, and many annotation-based issues would gone.

    This is wrong. If you use the default componentModel than when you use other mappers with Mapper#uses MapStruct will use the Mappers factory to create the used mapper or invoke it's default constructor. This is wrong.

    Having said that, the other part for annotating the methods might be interesting. However, I am not entirely sure about it. Your example doesn't really make sense to me, why would you want to cache the DTOs? Are you seeing a performance hit when the mapping is invoked?

  6. vadim-shb commented on Aug 5, 2018

    @vadim-shb
    vadim-shb
    on Aug 5, 2018
    Author
    More actions

    Your example doesn't really make sense to me

    It makes no sense for me either. I made it up quickly just to show that this functionality should be implemented for method annotations as well as for class annotations.

    It's general functionality and it may be used in lot's of ways by different frameworks.

    Let's made up a better example with Spring:

    public interface FooMapper {
      Foo map(Foo foo);
    }
    
    @Annotated({
      @Component,
      @Lazy,
      @Profile("prod")
    })
    @Mapper
    public interface FooMapperProd extends FooMapper {
      Foo map(Foo foo);
    }
    
    @Annotated({
      @Component,
      @Profile("qa")
    })
    @Mapper
    public interface FooMapperQa extends FooMapper {
      @Annotated({
        @Loggable
      })
      Foo map(Foo foo);
    }
    
    @Service
    public class SomeBean {
      @Inject
      FooMapper mapper;
    
      ...
    }
    

    I don't know an easy way to do such configuration now.

    Reacted by Martin Macko, Johan Steffens, Michel Erard, Bogdan Mariesan, Nick Buller, guleryuz, Baptiste Pernet-Mugnier, radhakrishnanch, XS, Bragolgirith and 1 more
  7. LinkedList commented on Oct 15, 2018

    @LinkedList
    LinkedList
    on Oct 15, 2018
    More actions

    I am too vouching for this, as now there is no way to add logical name to generated mapper.
    My problem could be also solved if there was a parameter on @Mapper that would provide value for @Component - see: https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/stereotype/Component.html#value--

    However provided example:

    @Annotated({
        @Component("foo")
    })
    

    would work as well..

  8. mjustin commented on Jul 16, 2021

    @mjustin
    mjustin
    on Jul 16, 2021
    More actions

    I just ran across a completely non-Spring situation where this could be useful. I marked one of my mapper methods as @Deprecated, since I hope to remove it in a future release, and want it documented in the code. However, since the generated method is neither @Deprecated nor has @SuppressWarnings("deprecation"), I get a compilation warning for the deprecation:

    /path/to/project/build/generated/sources/annotationProcessor/java/main/com/example/ExampleMapperImpl.java:90: warning: [deprecation] map(ExampleTarget) in ExampleMapper has been deprecated
        public ExampleTarget map(ExampleSource source) {
    

    Granted, this could (and perhaps should) be solved by a deprecation-specific MapStruct feature. However, it could also be solved by being able to specify that I want to add either of these annotations to the generated method.

    Reacted by Janusz, Henk Norman, payne911, Pavel Stepanov and Daniel Hammer
  9. famod commented on Sep 3, 2021

    @famod
    famod
    on Sep 3, 2021
    More actions

    My team just ran into this "missing feature": We use componentModel=cdi and mostly mapping interfaces.
    We now wanted to add cdi interceptor binding annotations to the generated MapperImpl classes, but there is no way to do that.

    As a workaround, we'll have to migrate to abstract mapping classes.

    Reacted by Michael Kroll, Chris and mjustin
  10. payne911 commented on Feb 23, 2022

    @payne911
    payne911
    on Feb 23, 2022
    More actions

    My team would love such a feature as well.
    We are trying to exclude the generated classes from the unit-test code coverage stats in our pipeline via a custom annotation. It would be great to be able to propagate that annotation to the implementation of the mappers.

    But maybe another way to approach this would be for MapStruct to have its own @Generated class and to change the Retention Policy from SOURCE to either RUNTIME or CLASS, as explained there. That is how Lombok does it.

    Reacted by Daniel Hammer
  11. Zegveld commented on Feb 23, 2022

    @Zegveld
    Zegveld
    on Feb 23, 2022
    Contributor
    More actions

    @payne911, why not have the generated classes live in a second source-folder which is not analyzed for coverage?
    Let's continue discussing this here, not to sidetrack this issue.

  12. Zegveld commented on Feb 27, 2022

    @Zegveld
    Zegveld
    on Feb 27, 2022
    Contributor
    More actions

    I've been looking into a way to implement this.

    I did not find a way to create an annotation that would allow the following notation:

    @AnnotateWith( {
        @Deprecated,
        @AnnotationWithParameters( param1="value", param2="otherValue" )
    } )
    

    What I did find that might work is the following, although the autocomplete behavior of the IDE would be lost:

    @AnnotateWith( Deprecated.class )
    @AnnotateWith( type=AnnotationWithParameters.class, parameters = {
        @AnnotateWith.Parameter ( key = "param1", value = "value" ),
        @AnnotateWith.Parameter ( key = "param2", value = "otherValue" )
    } )
    

    wdyt, @sjaakd and @filiphr ? Would this be the way to go, or is there perhaps a better way to support the initial idea?

  13. filiphr commented on Feb 28, 2022

    @filiphr
    filiphr
    on Feb 28, 2022
    Member
    More actions

    I've missed the comment about the @Deprecated from @mjustin:

    I just ran across a completely non-Spring situation where this could be useful. I marked one of my mapper methods as @Deprecated, since I hope to remove it in a future release, and want it documented in the code.

    I've created #2773 for this.

    Regarding the @Generated from @payne911:

    But maybe another way to approach this would be for MapStruct to have its own @Generated class and to change the Retention Policy from SOURCE to either RUNTIME or CLASS, as explained there. That is how Lombok does it.

    I do not agree with this because I do not agree that the code generated by MapStruct should not be tested and be unverified. We strive for generating code that looks like it has been written by a person, which means that it shouldn't have warnings. The generated code usually is simple, but there can be complex things due to the different configuration options we have, you should most definitely test that the mappings that are generated are mapping your data as you would expect.

    Regarding the proposal from @Zegveld.

    @AnnotateWith( {
       @Deprecated,
       @AnnotationWithParameters( param1="value", param2="otherValue" )
    } )

    That would be ideal, unfortunately Java doesn't allow doing that. Lombok has their experimental onX for supporting that, but that only works for them since they are changing the byte code of the class itself.

    What I did find that might work is the following, although the autocomplete behavior of the IDE would be lost:

    @AnnotateWith( Deprecated.class )
    @AnnotateWith( type=AnnotationWithParameters.class, parameters = {
       @AnnotateWith.Parameter ( key = "param1", value = "value" ),
       @AnnotateWith.Parameter ( key = "param2", value = "otherValue" )
    } )

    This is actually not that bad of an idea. We will need to provide parameters for the different parameter types of an annotation but I think it can work. People can also use their own composed meta annotations and use use @AnnotateWith( MyCustomAnnotation.class ). We will need to be a bit careful with the component model annotations that we are already adding, but it is doable.

  14. Zegveld commented on Mar 6, 2022

    @Zegveld
    Zegveld
    on Mar 6, 2022
    Contributor
    More actions

    We will need to be a bit careful with the component model annotations that we are already adding, but it is doable.

    Concerning that, I was thinking that perhaps the @Named annotation on the mapper could be transferred over as the name for the mapper in the annotation of the component model.
    For example:

    @Named( "MyOwnMapper" )
    @Mapper( componentModel = "spring" )
    interface Mapper {
    

    would result in:

    @Component( "MyOwnMapper" )
    class MapperImpl implements Mapper {
    

    Perhaps the above could be an issue by itself.

    Concerning the @AnnotateWith and @AnnotateWith.Parameter I think that we will get the following classes:

    @Repeatable( AnnotateWiths.class )
    @Retention( SOURCE )
    @Target( { TYPE, METHOD } )
    public @interface AnnotateWith {
        Class<? extends Annotation> value();
    
        Parameter[] parameters() default {};
    
        @interface Parameter {
            String key();
    
            String stringValue() default ""; // used for having the value as a string.
            String objectValue() default ""; // used when the value is not a string.
        }
    }
    

    Example:

    @AnnotateWith( MyCustomAnnotation.class )
    @AnnotateWith( value=MyCustomAnnotationWithParameters.class, parameters = {
       @AnnotateWith.Parameter ( key = "stringParam", stringValue = "value" ),
       @AnnotateWith.Parameter ( key = "classParam", objectValue = "java.lang.String.class" ),
       @AnnotateWith.Parameter ( key = "intParam", objectValue = "1234" )
    } )
    

    would result in:

    @MyCustomAnnotation
    @MyCustomAnnotationWithParameters( stringParam = "value", classParam = java.lang.String.class, intParam = 1234 )
    

    How extended to we want to protect the user against mistakes with @AnnotateWith usage?
    There are several things that can go wrong, that cause the generated mapper code to not compile:

    1. The annotation is not allowed at that location.
    2. The parameter does not exist on the annotation.
    3. The parameter value is of the wrong type.
  15. filiphr commented on Mar 6, 2022

    @filiphr
    filiphr
    on Mar 6, 2022
    Member
    More actions

    Concerning that, I was thinking that perhaps the @Named annotation on the mapper could be transferred over as the name for the mapper in the annotation of the component model.

    We can't do that, because @Named is used for qualifying methods / mappers. If a mapper is annotated with @Named then it won't be automatically used for mapping custom types.

    Concerning the @AnnotateWith and @AnnotateWith.Parameter I think that we will get the following classes:

    I don't like the stringValue and objectValue, I think that we need to have the actual values that are supported for an annotation.

    e.g.

    @Repeatable( AnnotateWiths.class )
    @Retention( CLASS )
    @Target( { TYPE, METHOD, ANNOTATION_TYPE } )
    public @interface AnnotateWith {
        Class<? extends Annotation> value();
    
        Parameter[] parameters() default {};
    
        @interface Parameter {
            String key();
    
            short[] shorts() default {};
    
            byte[] bytes() default {};
    
            int[] ints() default {};
    
            long[] longs() default {};
    
            float[] floats() default {};
    
            double[] doubles() default {};
    
            char[] chars() default {};
    
            boolean[] booleans() default {};
    
            String[] strings() default {};
    
            Class<?>[] classes() default {};
        }
    }

    This makes it easy to use statics and so on. This is similar to the JUnit 5 ValueSource. An annotation also allows having enums, but for those we will have to use the strings and determine the enum value on our own.

    Also the ElementType should be:

    • TYPE
    • METHOD
    • ANNOTATION_TYPE - in order to allow meta annotations

    With the the example would look like:

    @AnnotateWith( MyCustomAnnotation.class )
    @AnnotateWith( value=MyCustomAnnotationWithParameters.class, parameters = {
       @AnnotateWith.Parameter ( key = "stringParam", strings = "value" ),
       @AnnotateWith.Parameter ( key = "classParam", classes = String.class ),
       @AnnotateWith.Parameter ( key = "intParam", ints = 1234 )
    } )

    How extended to we want to protect the user against mistakes with @AnnotateWith usage?

    We want to provide as much information to the user as possible. We can easily detect certain things so we can easily

    There are several things that can go wrong, that cause the generated mapper code to not compile:

    1. The annotation is not allowed at that location.

    We can have a compile error pointing out the mistake.

    1. The parameter does not exist on the annotation.

    Again, compile error pointing out the mistake.

    1. The parameter value is of the wrong type.

    Also a compile error pointing out that that it isn't supported.

    Reacted by mk868
  16. 38 remaining items

  17. Zegveld
    added 9 commits that reference this issue on Jun 5, 2022

    mapstruct#1574: added compiler error for defining same parameter twic…

    2cb8605

    mapstruct#1574: Added compiler error for completely identical annotat…

    8bfba39

    mapstruct#1574: added examples to javadoc. fixed line length.

    7e76cd6

    mapstruct#1574: remove paragraph tags

    8dbb1f7

    mapstruct#1574: converted the more complex tests into fixture tests.

    f9ba5c1

    mapstruct#1574: introducing a NullEnum util class as default class fo…

    5c60c3d

    mapstruct#1574: fix javadoc

    b8045a0

    mapstruct#1574: fix line ending

    9117141

    mapstruct#1574: add missing license header.

    5e834d6
  18. Zegveld
    added a commit that references this issue on Jul 31, 2022

    mapstruct#1574: Too many element parameters then specific error message.

    701709f
  19. filiphr
    closed this as completedin #2792on Aug 20, 2022
  20. filiphr
    added a commit that references this issue on Aug 20, 2022

    #1574: Support for annotating the generated code with custom annotations

    849085e
  21. filiphr
    added
    feature
    on Oct 10, 2023
  22. NadChel commented on May 22, 2025

    @NadChel
    NadChel
    on May 22, 2025
    More actions

    Can I ask what is the rationale behind this design decision involving class literals? As opposed to what the OP suggested

      @Annotated(@Cacheable(value="cache", key="mappedFoo #foo.id" ))

    It appears more straightforward and flexible than

      @AnnotatedWith(value=Cacheable.class, elements={@Element(strings="cache"), @Element(name=key, strings="mappedFoo #foo.id")})

    I'm sure there was reasoning behind it that I don't yet realize

  23. filiphr commented on May 25, 2025

    @filiphr
    filiphr
    on May 25, 2025
    Member
    More actions

    @NadChel, the proposed option is not valid Java. I think that we couldn't provide an interface where you can just add any annotated element in it. If you can provide us with a valid java code that can be used as

    @Annotated(@Cacheable(value="cache", key="mappedFoo #foo.id" ))

    we are more than welcome to adjust it. Please keep in mind, that if you refer to the way that Lombok does this using the onX, that's not valid Java and we have no plans to go down the path of Lombok to implement such a feature outside of using the public Java API

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

  • @Zegveld
    Zegveld

Labels

feature

Type

No type

Projects

No projects

    Milestone

    • 1.6.0.Beta1

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions

    You are visitor 01402503 — GeezerVison does not store the pages you look up.
    Best viewed with Netscape Navigator 4.7 or Internet Explorer 6 at 800×600. No cookies required.