12

I'm refactoring my code. I want to use java records instead of java class in my DTO. To convert DTO to Entity, I'm using ModelMapper (version 2.3.5). When I try to get info about user (call method co convert Entity to DTO) I get this error.

Failed to instantiate instance of destination xxx.UserDto. Ensure that xxx.UserDto has a non-private no-argument constructor.

This is my code.

public record UserDto(String firstName,
                      String lastName,
                      String email,
                      String imageUrl) {}

@RestController
public class UserController {

    @Autowired
    private UserRepository userRepository;

    @Autowired
    private ModelMapper modelMapper;


    @GetMapping("/user/me")
    @PreAuthorize("hasRole('USER')")
    public UserDto getCurrentUser(@CurrentUser UserPrincipal userPrincipal) {
        return convertToDto(userRepository.findById(userPrincipal.getId())
                .orElseThrow(() -> new ResourceNotFoundException("User", "id", userPrincipal.getId())));
    }


    private UserDto convertToDto(User user) {
        UserDto userDto = modelMapper.map(user, UserDto.class);
        return userDto;
    }

    private User convertToEntity(UserDto userDto) throws Exception {
        User post = modelMapper.map(userDto, User.class);
        return post;
    }
}

Edit: Updating to version 2.3.8 doesn't help!

Naman
  • 27,789
  • 26
  • 218
  • 353
Pawel
  • 427
  • 2
  • 7
  • 15
  • Can you try using version `2.3.8` of `ModelMapper`? The version that you're using was created before `record`s were released as a preview feature. – Jacob G. Jun 18 '20 at 17:11
  • Updated, but not working. – Pawel Jun 18 '20 at 17:17
  • Then `ModelMapper` does not yet support `record`. I recommend opening an issue on their [GitHub](https://github.com/modelmapper/modelmapper) repository. – Jacob G. Jun 18 '20 at 17:17
  • So what other tools, should I select to map dto to entity? – Pawel Jun 18 '20 at 17:22
  • 1
    related to the resolution of the no-argument constructor, - [Define default constructor for record](https://stackoverflow.com/questions/61152337/define-default-constructor-for-record) – Naman Jun 18 '20 at 17:52
  • 2
    As @JacobG. said and just in case it works out, here is a tracker on GitHub - [#modelmapper/issues/546](https://github.com/modelmapper/modelmapper/issues/546) – Naman Jun 18 '20 at 18:01

2 Answers2

25

The fields of a record are final, so they must be set through the constructor. Many frameworks will cheat and use various tricks to modify final fields after the fact anyway, but these will not work on records. If you want to instantiate a record, you have to provide all the field values at construction time.

It may take a little time for frameworks to learn about records. The old model of "call a no-arg constructor, then set the fields" will not work for records. Some frameworks are already able to deal with this (e.g., "constructor injection"), while others are not yet there. But, we expect that frameworks will get there soon enough.

As the commenters said, you should encourage your framework provider to support them. It isn't hard.

Brian Goetz
  • 90,105
  • 23
  • 150
  • 161
  • 2
    Yes mr architect but it is still a language feature in 14 so lombok sounds pretty promising till the feature gets delivered. –  Jun 20 '20 at 14:23
4

record is a preview feature in Java 14 so I would recommend you to not use it on production. Records were finalized in Java 16. Secondly, it doesn't mimic java bean.

record doesn't have a default no arg constructor implicitly if there are fields. If you wrote a no arg constructor, you will have to delegate the call to all args constructor and since all the fields are final you can only set them once. So you are kind of stuck there. See JEP 359:

It is not a goal to declare "war on boilerplate"; in particular, it is not a goal to address the problems of mutable classes using the JavaBean naming conventions.

An alternative that works today would be to use Lombok. Example of UserDto using Lombok:

@NoArgsConstructor
@AllArgsConstructor
@Data
public class UserDto {
    private String firstName;
    private String lastName;
    private String email;
    private String imageUrl;
}
Brian Goetz
  • 90,105
  • 23
  • 150
  • 161
Aniket Sahrawat
  • 12,410
  • 3
  • 41
  • 67
  • 1
    ..additionally for a Lombok vs Record decision, here is a [link to another Q&A](https://stackoverflow.com/questions/61306802/lombok-getter-setter-vs-java-14-record) – Naman Jun 18 '20 at 17:45
  • 1
    Yes, till it relies on the no arg constructor to create a object, you can use lombok. – Aniket Sahrawat Jun 18 '20 at 17:49
  • Thanks for your help, but I have decided to use other mapper - MapStruct. In this tools problem with contructor is avoid – Pawel Jun 19 '20 at 15:39