"Ambiguous constructors found" error when there more than one constructor when using MapStruct
Asked Answered
M

5

9

I'm new to mapstruct. I'm trying to map ItemInfo to Item object and the following are my classes.

public class ItemInfo {

    private String itemOwner;
    private String itemOwnerArea;

    //getters and setters
}

Following is the class that I'm planing to convert to

public class Item {
    private UserInfo owner;
    // getters and setters.
}

UserInfo class (Note that it has two constructors)

public class UserInfo {

    private final String username;
    private final String area;

    public UserInfo(String username, String area){
        //set 
    }

    public UserInfo(String info) {
        //set
    }

    // getters only
}

I have created the mapper interface as below

@Mapper
public interface ItemMapper {
    ItemMapper INSTANCE = Mappers.getMapper(ItemMapper.class);

    @Mapping(source = "itemOwner", target = "owner.username")
    @Mapping(source = "itemOwnerArea", target = "owner.area")
    Item mapItemInfoToItem(ItemInfo itemInfo);
}

When I build this, I get the following error

 Ambiguous constructors found for creating com.app.test.UserInfo. Either declare parameterless 
constructor or annotate the default constructor with an annotation named @Default.

Can you guide me please?

Update.

As mentioned by @AnishB I added a default constructor but I get a different error

Property "username" has no write accessor in UserInfo for target name "owner.username".

Thanks

Michey answered 3/11, 2020 at 5:36 Comment(2)
Do you have default constructor in UserInfo ?Lorenzetti
@AnishB. no. only these two constructorsMichey
T
13

We are working on improving the error message in this place.

From the current error message

Ambiguous constructors found for creating com.app.test.UserInfo. Either declare parameterless constructor or annotate the default constructor with an annotation named @Default.

You have 2 options:

  • Create an empty constructor and MapStruct will use it. This is however not needed and I would strongly recommend not to use it.
  • Create your own @Default annotation and annotate the default constructor that MapStruct should use. I would highly recommend this approach.

MapStruct is not shipping this annotation because it is focused on bean mapping and doesn't want to pollute the entity code with annotations from MapStruct. Therefore it can use any Default annotation from any package.

e.g.

package foo.support.mapstruct;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Target(ElementType.CONSTRUCTOR)
@Retention(RetentionPolicy.CLASS)
public @interface Default {
}

You already did that and the next error message:

Property "username" has no write accessor in UserInfo for target name "owner.username".

Is there saying that there is no write accessor for the username, because most likely you have annotated the single parameter constructor. If you've done that then MapStruct only knows about the area and not about the username. Another option for that error is that you have added an empty parameterless constructor and no setters.

You most likely need

@Default
public UserInfo(String username, String area){
    //set 
}
Tehuantepec answered 3/11, 2020 at 7:8 Comment(2)
Thank you @Filip, that worked for me! This ambiguous error still exists on 1.4.2.Final (I haven't tested the 1.5 RC). Would you be interested in a pull request that clarifies the error message and instructions?Robber
Yes feel free to provide a PR with an improved error message and instructionsTehuantepec
L
4

Mapstruct needs a default empty constructor for its functioning.

So, update the UserInfo class to have a default empty constructor:

public class UserInfo {

    private final String username;
    private final String area;

    public UserInfo(){
        
    }

    public UserInfo(String username, String area){
        //set 
    }

    public UserInfo(String info) {
        //set
    }

    // setters and getters
}

Or annotate anyone constructor with @Default so that mapstruct can use that constructor as a default constructor.

For example :

 @Default
 public UserInfo(String info) {
    //set
 }

Also, you have to add setters in UserInfo as well.

Lorenzetti answered 3/11, 2020 at 5:47 Comment(6)
I was trying to achieve this without modifying the original class (UserInfo). My intension was to find something to do in ItemMapperMichey
@ChamilaAdhikarinayake This might not be possible I guess.Lorenzetti
@ChamilaAdhikarinayake You have to create getters and setters in UserInfo class. You didn't add setters in UserInfo. Then, how it will work if you don't have setters?Lorenzetti
I couldn't find this Default annotation as well. Do I hv to import any package for this?Michey
@ChamilaAdhikarinayake Yes !! @Default annotation can be used from any package as per Mapstruct Docs.Lorenzetti
This is not entirely correct for 1.4 @Anish. In 1.4 support fir mapping with constructors, without setters was addedTehuantepec
I
0

Here is the documentation: https://github.com/mapstruct/mapstruct/blob/master/documentation/src/main/asciidoc/chapter-3-defining-a-mapper.asciidoc#using-constructors

Use @Default annotation to mark default constructor.

Icarus answered 3/11, 2020 at 5:53 Comment(0)
H
0

I faced this error when I run a Junit5 single test method. But, when I run the test using mvn test, the project and all tests were run successfully.

I did not create/add a @Default annotation to solve this error because I did not get an error like this when I run the mvn test command. However, I solved it by changing the pom.xml file.

pom.xml file (before).

<!-- Under </annotationProcessorPaths> tag -->
<path>
    <groupId>org.projectlombok</groupId>
    <artifactId>lombok</artifactId>
    <version>1.18.4</version>
</path>

Then, I check the lombok documentation again and I used version: 1.18.24. It worked for me.

  • So, my solution is to change the version as below.

pom.xml file (After changes).

<path>
    <groupId>org.projectlombok</groupId>
    <artifactId>lombok</artifactId>
    <version>1.18.24</version>
</path>

References:

  1. https://projectlombok.org/setup/maven
  2. java: You aren't using a compiler supported by lombok, so lombok will not work and has been disabled
Hereby answered 7/8, 2022 at 19:16 Comment(0)
F
0

You can use an @ObjectFactory to handle creation of the instance manually.

Example:

@Mapper(uses = { ItemFactory.class }) // Reference the factory
public interface ItemMapper {
    Item mapItemInfoToItem(ItemInfo itemInfo);
}
@Component // Spring component annotation
public class ItemFactory {

    // @ObjectFactory tells mapstruct to use this method when creating such an instance.
    // This has precedence over constructors, even when they are marked with @Default.
    @ObjectFactory
    Item mapItemInfoToItem(ItemInfo itemInfo) {
        Item item = new Item();
        item.setUserInfo(new UserInfo(itemInfo.getItemOwner(), itemInfo.getItemOwnerArea()));
        return item;
    }
}

Note that this also works when you do not have access to the original classes, e.g. because they are coming from third-party library.

Foxhound answered 20/6 at 12:49 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.