Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[BUG] Configuration bindings prints value but at runtime throws NullPointerException Version: 7.1.3 #409

Open
thiagohora opened this issue Nov 7, 2024 · 6 comments

Comments

@thiagohora
Copy link

NullPointerException when @Inject a particular configuration property

ru.vyarus.dropwizard.guice.debug.YamlBindingsDiagnostic: Available configuration bindings = 
...

Unique sub configuration objects bindings:

  MyConfig.batchOperations
            @Config BatchOperationsConfig = BatchOperationsConfig(datasets=DatasetsConfig[test=2])
    @Inject
    public MyService(@Config @NonNull BatchOperationsConfig batchOperationsConfig) {
       ...
@thiagohora thiagohora changed the title Configuration bindings prints value but at runtime throws NullPointerException [BUG] Configuration bindings prints value but at runtime throws NullPointerException Nov 7, 2024
@thiagohora thiagohora changed the title [BUG] Configuration bindings prints value but at runtime throws NullPointerException [BUG] Configuration bindings prints value but at runtime throws NullPointerException Version: 7.1.3 Nov 7, 2024
@xvik
Copy link
Owner

xvik commented Nov 8, 2024

Could you please prepare an example project?

I tried to reproduce this, but it works. There must be some important specific in your project, not mentioned here.
I can't imagine what could it be (and so I need some sample project to reproduce and investigate this problem).

The same configuration introspection data is used for configuration report and for actual bindings, so if report shows something - it should be bound in context. Also, as it shows value in report, the same value must be bound - have no idea how null could be bound instead (and we could be sure that binding exists because otherwise guice would complain).

You could also try to enable guice report:

.printAllGuiceBindings()

which shows all actual guice bindings (whreas configurtation report shows bindings that should be registered). It does not show the binding value (probably good idea for a new report), but you'll see if @Config BatchOperationsConfig binding is available at all.
Not sure if it will show anything new because we already know the binding exists (maybe it will push you into right direction)

@thiagohora
Copy link
Author

thiagohora commented Nov 8, 2024

@Singleton
@RequiredArgsConstructor(onConstructor_ = @Inject)
@Slf4j
class DatasetServiceImpl implements DatasetService {

    private static final String DATASET_ALREADY_EXISTS = "Dataset already exists";

    private final @NonNull IdGenerator idGenerator;
    private final @NonNull TransactionTemplate template;
    private final @NonNull Provider<RequestContext> requestContext;
    private final @NonNull EntitytDAO entityDAO;
    private final @NonNull @Config BatchOperationsConfig batchOperationsConfig;

    @Override
    public DatasetPage find(int page, int size, @NonNull DatasetCriteria criteria) {
        String workspaceId = requestContext.get().getWorkspaceId();
        String userName = requestContext.get().getUserName();

        int maxExperimentInClauseSize = batchOperationsConfig.getDatasets().getMaxExperimentInClauseSize();
        //...
     }   

Config class:

@Getter
public class MyAPPConfiguration extends Configuration {

    @Valid
    @NotNull @JsonProperty
    private DataSourceFactory database = new DataSourceFactory();

    @Valid
    @NotNull @JsonProperty
    private CorsConfig cors = new CorsConfig();

    @Valid
    @NotNull @JsonProperty
    private BatchOperationsConfig batchOperations = new BatchOperationsConfig();
}

Lombok Config

lombok.copyableAnnotations += jakarta.inject.Named
lombok.copyableAnnotations += ru.vyarus.dropwizard.guice.module.yaml.bind.Config

Error

ERROR [2024-11-08 14:34:35,225] io.dropwizard.jersey.errors.LoggingExceptionMapper: Error handling a request: a3c206a253a770ab
2024-11-08T14:34:35.241199214Z ! java.lang.NullPointerException: Cannot invoke "com.my.project.infrastructure.BatchOperationsConfig$DatasetsConfig.getMaxExperimentInClauseSize()" because the return value of "com.my.project.infrastructure.BatchOperationsConfig.getDatasets()" is null
2024-11-08T14:34:35.241203214Z ! at com.my.project.domain.DatasetServiceImpl.find(DatasetService.java:261)
2024-11-08T14:34:35.241205464Z ! at com.my.project.api.resources.v1.priv.DatasetsResource.findDatasets(DatasetsResource.java:127)
2024-11-08T14:34:35.241206339Z ! at java.base/jdk.internal.reflect.DirectMethodHandleAccessor.invoke(DirectMethodHandleAccessor.java:103)
2024-11-08T14:34:35.241207131Z ! at java.base/java.lang.reflect.Method.invoke(Method.java:580)
2024-11-08T14:34:35.241208423Z ! at org.glassfish.jersey.server.model.internal.ResourceMethodInvocationHandlerFactory.lambda$static$0(ResourceMethodInvocationHandlerFactory.java:52)
2024-11-08T14:34:35.241209298Z 

Log from print:

─ ConfigBindingModule          (r.v.d.g.m.yaml.bind)      
2024-11-08T14:07:13.800219219Z     │       ├── instance             [@Singleton]     ConfigurationTree                               at ru.vyarus.dropwizard.guice.module.yaml.bind.ConfigBindingModule.configure(ConfigBindingModule.java:52)
2024-11-08T14:07:13.800220135Z     │       ├── instance             [@Singleton]     Configuration                                   at ru.vyarus.dropwizard.guice.module.yaml.bind.ConfigBindingModule.bindRootTypes(ConfigBindingModule.java:102)
2024-11-08T14:07:13.800221094Z     │       ├── instance             [@Singleton]     MyAPPConfiguration                               at ru.vyarus.dropwizard.guice.module.yaml.bind.ConfigBindingModule.bindRootTypes(ConfigBindingModule.java:102)
2024-11-08T14:07:13.800222010Z     │       ├── instance             [@Singleton]     @Config Configuration                           at ru.vyarus.dropwizard.guice.module.yaml.bind.ConfigBindingModule.bindRootTypes(ConfigBindingModule.java:104)
2024-11-08T14:07:13.800224010Z     │       ├── instance             [@Singleton]     @ConfigMyAPPConfiguration                       at ru.vyarus.dropwizard.guice.module.yaml.bind.ConfigBindingModule.bindRootTypes(ConfigBindingModule.java:104)
2024-11-08T14:07:13.800224927Z     │       ├── instance             [@Singleton]     @Config AdminFactory                            at ru.vyarus.dropwizard.guice.module.yaml.bind.ConfigBindingModule.bindUniqueSubConfigurations(ConfigBindingModule.java:117)
2024-11-08T14:07:13.800225844Z     │       ├── instance             [@Singleton]     @Config AuthenticationConfig                    at ru.vyarus.dropwizard.guice.module.yaml.bind.ConfigBindingModule.bindUniqueSubConfigurations(ConfigBindingModule.java:117)
2024-11-08T14:07:13.800226760Z     │       ├── instance             [@Singleton]     @Config BatchOperationsConfig                   at ru.vyarus.dropwizard.guice.module.yaml.bind.ConfigBindingModule.bindUniqueSubConfigurations(ConfigBindingModule.java:117)
2024-11-08T14:07:13.800227635Z     │       ├── instance             [@Singleton]     @Config CorsConfig                  

@xvik
Copy link
Owner

xvik commented Nov 10, 2024

Sorry, I can't reproduce this. There must be some side effect: either DatasetServiceImpl.batchOperationsConfig instance is different from MyAPPConfiguration.batchOperations or somewhere BatchOperationsConfig.setDatasets(null) is called.

One way to verify is by injecting MyAPPConfiguration in constructor to be able to compare instances:

@Singleton
@RequiredArgsConstructor(onConstructor_ = @Inject)
@Slf4j
class DatasetServiceImpl implements DatasetService {

    private static final String DATASET_ALREADY_EXISTS = "Dataset already exists";

    private final @NonNull IdGenerator idGenerator;
    private final @NonNull TransactionTemplate template;
    private final @NonNull Provider<RequestContext> requestContext;
    private final @NonNull EntitytDAO entityDAO;
    private final @NonNull @Config BatchOperationsConfig batchOperationsConfig;
    private final @NonNull MyAPPConfiguration configuration;

    @Override
    public DatasetPage find(int page, int size, @NonNull DatasetCriteria criteria) {
        String workspaceId = requestContext.get().getWorkspaceId();
        String userName = requestContext.get().getUserName();

        // must be the same instance
         System.out.println(System.idenityHashCode(configuration.getBatchOperationsConfig()))
         System.out.println(System.idenityHashCode(batchOperationsConfig))

        // must both be null
        System.out.println(configuration.getBatchOperationsConfig().getDatasets())
        System.out.println(batchOperationsConfig().getDatasets())

        int maxExperimentInClauseSize = batchOperationsConfig.getDatasets().getMaxExperimentInClauseSize();
        //...
     } 

If you'll make sure that BatchOperationsConfig is the same object then something sets null into datasets: try to debug setter (or println inside setter to track interactions)

Or, to avoid sharing project in public, you can send it directly to my email vyarus[at]gmail.com and I'll investigate the probelm.

@thiagohora
Copy link
Author

thiagohora commented Nov 10, 2024 via email

@xvik
Copy link
Owner

xvik commented Nov 13, 2024

I run all tests with correto 21 and everything works (it would be very strange if it didn't).

I assume that the simplest explanation could be server configuration file (yaml).
If BatchOperationsConfig did not initialize DatasetsConfig and there is no configuration for it in yaml file, then it would remain null.
But, in this case, configuration report should also show null values.

No other ideas. Only debug on real case could help.

@thiagohora
Copy link
Author

I will try to create a public repo with this issue reproduced

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants