How To @Inject Property File Properties With CDI

Also published on DZone

Challenge

You use CDI for your Java application and want to use a Property file for some needed configuration, but don’t know how to do this …

Solution

Create producers and an annotation to direct it.

Annotation

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
package nl.ivonet.service.config;

import javax.enterprise.util.Nonbinding;
import javax.inject.Qualifier;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;

import static java.lang.annotation.ElementType.FIELD;
import static java.lang.annotation.ElementType.METHOD;
import static java.lang.annotation.ElementType.PARAMETER;
import static java.lang.annotation.ElementType.TYPE;
import static java.lang.annotation.RetentionPolicy.RUNTIME;

/**
* Represents an property key to be injected
*/
@Qualifier
@Retention(RUNTIME)
@Target({TYPE, METHOD, FIELD, PARAMETER})
public @interface Property {
/**
* Key to search for in the property files.
*
* @return a string.
*/
@Nonbinding String value() default "";

/**
* Is the key a mandatory key.
*
* @return true as default but false if set
*/
@Nonbinding boolean required() default true;
}

Producer

The examples provided here enable parsing of Strings, Booleans and Integers but you can of course add producers as needed.
Note that the getKey method firsts looks if the @Property annotation has a value and if so it will take that as the key for the property file, but if you don’t provide a value it will take the annotated field name as the key.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
package nl.ivonet.service.config;

import javax.annotation.PostConstruct;
import javax.enterprise.inject.Produces;
import javax.enterprise.inject.spi.InjectionPoint;
import java.io.IOException;
import java.io.InputStream;
import java.util.Properties;

public class PropertyProducer {
private Properties properties;

@Property
@Produces
public String produceString(final InjectionPoint ip) {
return this.properties.getProperty(getKey(ip));
}

@Property
@Produces
public int produceInt(final InjectionPoint ip) {
return Integer.valueOf(this.properties.getProperty(getKey(ip)));
}

@Property
@Produces
public boolean produceBoolean(final InjectionPoint ip) {
return Boolean.valueOf(this.properties.getProperty(getKey(ip)));
}

private String getKey(final InjectionPoint ip) {
return (ip.getAnnotated()
.isAnnotationPresent(Property.class) &&
!ip.getAnnotated()
.getAnnotation(Property.class)
value().isEmpty()) ? ip.getAnnotated()
.getAnnotation(Property.class)
.value()
: ip.getMember()
.getName();
}

@PostConstruct
public void init() {
this.properties = new Properties();
final InputStream stream = PropertyProducer.class
.getResourceAsStream("/application.properties");
if (stream == null) {
throw new RuntimeException("No properties!!!");
}
try {
this.properties.load(stream);
} catch (final IOException e) {
throw new RuntimeException("Configuration could not be loaded!");
}
}
}

Usage

1
2
3
@Inject
@Property
private String rootFolder;

or

1
2
3
@Inject
@Property("root.folder.key.here")
private String rootFolder;

application.properties

1
2
3
rootFolder=/helloWorldFolder
root.folder.key.here=/another/folder/name
elasticsearchUrl=http://localhost:9200

Extra consideration

Note that when using this method for injecting Strings and stuff it might get difficult to write Unit tests as Mocking frameworks cannot Mock final classes (e.g. String).
If you get to this problem you might want to look at FieldInjection article