A fun evening with Quarkus, Lombok and debugging dependency injection issues
Recently, I have had the joy of dealing with a rather interesting issue, one that came about due to the joint use of Quarkus dependency injection and the popular Lombok library, and of course, due to lack of reading library documentation thoroughly.
A bit about Lombok
Among Lombok’s set of helpful features are two annotations that generate constructors from given instance variables:
@RequiredArgsConstructor
: generates the constructor with a parameter for every class field that requires special handling. In practice these are usually those fields that are marked asfinal
@AllArgsConstructor
: generates the constructor with a parameter for every class field from all non-final
Both annotations allow additional parameters. The one we’re interested in is the onConstructor_ = { <annotations> }
, which instructs Lombok to take the annotations you specified and put them onto the generated constructor:
@AllArgsConstructor(onConstructor_ = { @Inject })
public class Song {
String title;
}
// Lombok generates the following constructor:
@java.beans.ConstructorProperties({"title"})
@Inject
public Song(String title) {
this.title = title;
}
The option to add annotations to generated constructors is useful, because of how well this plays together with Quarkus. In order to use constructor dependency injection, Quarkus assumes that you will annotate the constructer with the @Inject
annotation, although it’s not always necessary 1.
So in practice, what you might do is:
@AllArgsConstructor(onConstructor_ = { @Inject })
public class SongService() {
Dao<Song> songDao;
SongValidationService validationService;
... and several methods ...
}
and it will work splendidly. But there is a small corner case, which comes into play if you add custom qualifiers for the beans you are trying to inject.
A bit about Quarkus and the issue I ran into
Quarkus identifies whether a bean matches an injection point by the type of the bean. What might happen is that you have multiple beans of the same type, but with different configuration on initialization of the bean, and you have to distinguish between them somehow, otherwise Quarkus will be unable to build your project.
With that in mind, suppose that we want to distinguish between different SongValidationService
beans; we could annotate the field here with a custom qualifier, let’s call it @CommunitySongValidator
:
...
@CommunitySongValidator
SongValidationService validationService;
...
Now, this doesn’t look particularly different from the example above. There’s just one change; the additional qualifier annotation. However, this won’t work without additional configuration :'(
What might not be completely obvious at first glance is the fact that Lombok will not copy the field annotations into the generated constructors by default. What happens instead is that Lombok generates the constructor without the field parameters and if you have multiple beans of the same type, which you distinguish by setting qualifier annotations, Quarkus will fail with AmbiguousResolutionException
, since it’ll find more than just one bean matching the injection point.
A simple solution
The solution is, of course, embarrassingly simple and straightforward, provided that you thoroughly read the documentation and make note of this. Of course, you often don’t, or the library changes, your usage of the library changes, etc., and your knowledge has some holes that manifest themselves into funny, albeit frustrating bugs.
To solve the issue at hand, we have to modify Lombok’s configuration file lombok.config
with the following line
lombok.copyableAnnotations += <package.of.your.annotation>
and voila. Lombok will now preserve the annotations on the fields when generating the constructor and Quarkus’ dependency injection will work as expected, since only the bean annotated with @CommunitySongValidator
will match the injection point.
@Inject
is not always mandatory. This is because Quarkus has ‘Simplified Constructor Injection’. You can read more about the concepts behind it by following the link. ↩︎