Spring List and Map in Properties

We have the properties file, which is really useful, we can write DB URLs and passwords that can be changed without recompiling the app. Even better if we could have a list and a map in the Spring properties file.

But we can! Although it was a bit hard for me to find this, amongst the Converters, Binders and BindingResults, truth is if you are using SpringBoot you don’t need to write any custom converters. Spring Boot supports this out of the box.

Further reading https://docs.spring.io/spring-boot/docs/current/reference/html/boot-features-external-config.html

Spring Custom Object, List and Map in Properties file example

What if we want to define a custom object in properties? Then a list of those objects. Or a Map of values.

Let’s consider this properties file.

  • There is a big object app.
  • It has a collection (List) called userType, that has two objects.
  • These objects have 3 fields:
    • type,
    • description
    • a collection called rights
      • with objects that have two properties:
        • exclude (List)
        • canModify (List)
  • It also has a Map of String, String.
app.userType[0].type=1
app.userType[0].description=admin
app.userType[0].rights.exclude=50
app.userType[0].rights.canModify=10,20,30

app.userType[1].type=2
app.userType[1].description=user
app.userType[1].rights.exclude=50,40
app.userType[1].rights.canModify=20,30

app.commands.func1:EditUser
app.commands.func2:AddUser

This seems a complicate enough properties example for us to need to use List and Map in the Spring Boot app. I’m stressing Spring Boot because I don’t think this works in SpringFramework (non boot).

Let’s create this structure in java files.

package net.superglobals.properties;

import java.util.List;

public class Rule {
	private List<Long> canModify;
	private List<Long> exclude;

	public List<Long> getCanModify() {
		return canModify;
	}
	
	public void setCanModify(List<Long> canModify) {
		this.canModify = canModify;
	}
	
	public List<Long> getExclude() {
		return exclude;
	}
	
	public void setExclude(List<Long> allButExcluded) {
		this.exclude = allButExcluded;
	}
}

The UserConfig.java

package net.superglobals.properties;

public class UserConfig {
	private int type;
	private String description;
	private Rule rights;
	
	public String getDescription() {
		return description;
	}
	
	public void setDescription(String description) {
		this.description = description;
	}
	
	public Rule getRights() {
		return rights;
	}
	
	public void setRights(Rule rights) {
		this.rights = rights;
	}
	
	public int getType() {
		return type;
	}
	
	public void setType(int type) {
		this.type = type;
	}
}

The ‘app’ (Settings.java) file.

package net.superglobals.properties;

import java.util.List;
import java.util.Map;

import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.PropertySource;
import org.springframework.stereotype.Component;

@Component
@PropertySource("classpath:my_properties.properties")
@ConfigurationProperties("app")
public class Settings {
	private List<UserConfig> userType;
	private Map<String, String> commands;
	
	public List<UserConfig> getUserType() {
		return userType;
	}
	
	public void setUserType(List<UserConfig> userType) {
		this.userType = userType;
	}
	
	public Map<String, String> getCommands() {
		return commands;
	}
	
	public void setCommands(Map<String, String> commands) {
		this.commands = commands;
	}
	
	public UserConfig search(Integer type) {
		for (UserConfig c : userType) {
			if (c.getType() == type) {
				return c;
			}
		}
		return null;
	}
}

Notice the class have different names. In the properties you write just the field name, not the actual type (makes sense).

A simple Controller.

package net.superglobals.properties;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequestMapping("/test")
public class Controller {
	@Autowired
	private Settings settings;
	
	@GetMapping("/prop")
	public Settings getConfig() {
		return settings;
	}
}

Last but not least the file containing the main method. Notice the annotations.

package net.superglobals.properties;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.context.properties.EnableConfigurationProperties;

@SpringBootApplication
@EnableConfigurationProperties
public class PropertiesApplication {

	public static void main(String[] args) {
		SpringApplication.run(PropertiesApplication.class, args);
	}

}

Go to http://localhost:8080/test/prop to test.

We get this response that has a List and a Map taken from the properties file of a Spring Boot application!

{"userType":[{"type":1,"description":"admin","rights":{"canModify":[10,20,30],"exclude":[50]}},{"type":2,"description":"user","rights":{"canModify":[20,30],"exclude":[50,40]}}],"commands":{"func2":"AddUser","func1":"EditUser"}} 
FacebookTwitterLinkedin