George Vardikos
George Vardikos

Reputation: 2437

@Autowire within a DTO object - produces NullPointerException

On a spring boot 2.4.3 application with Java:

I am using a DTO to construct the JSON response from the domain model of the application. The DTO is just a plain java object.

I am trying to property inject a new class that I created for data transformation using the @Autowired but I get a nullPointerException on the runtime.

@Data
@NoArgsConstructor
public class FetchSupplierDTO {
    private long id;
    private String name;
    private String description;
    private String info;
    private List<String> tags;

    @Autowired
    private TagTranslation tagTranslation;

    public FetchSupplierDTO(SupplierEntity supplier) {
        this.id = supplier.getId();
        this.name = supplier.getDisplayName();
        this.description = supplier.getGivenDescription();
        this.info = supplier.getInfo();

        if (supplier.getTags() != null) {
            this.tags = tagTranslation.extractTagsFromEntity(supplier.getTags());
        }
     }
}

@Service
public class TagTranslation {

    public List<String> extractTagsFromEntity(List<TagEntity> tagEntityList) {
        List<String> tagStringList = new ArrayList<>();
        tagEntityList.forEach(productTag -> { tagStringList.add(productTag.getTag()); });
        return tagStringList;
    }
}

Upvotes: 0

Views: 2447

Answers (1)

Daan
Daan

Reputation: 126

First of all, looking at the current code I would design it so that the caller of the constructor is responsible for calling the autowired service. Then the DTO really stays a DTO and is not at the same time responsible for calling a service.

If there is really no way around calling a Spring component from inside a DTO then you will have to get the component manually. And call SpringBeanLocator.getBean(TagTranslation.class) to get that component and insert it in your field.

Spring holds a single instance of each component, on initialization it scans for autowired annotations within annotated classes (@Component, @Service) and initializes those fields. Once you call the constructor of such a class separately, it will not return the instance that is maintained by Spring, it will construct a new instance. Therefore it's autowired fields will be null.

public class SpringBeanLocator implements ApplicationContextAware {

  private static ApplicationContext context;

  @Override
  public final void setApplicationContext(ApplicationContext context) {
    validate(getInternalContext());
    setContext(context);
  }

  public static <T> T getBean(Class<T> type) {
    context.getBean(type);
  }
}

Upvotes: 1

Related Questions