…with real examples.

In Java applications, java.util.Mapis far more than a simple key-value store. Whether you're managing configuration files, forming API responses, or calculating grouped statistics, working with structured data is essential.
At work, I often need to use advanced Map operations. When they fit the need, I apply them. Hence, sharing the learnings in this article.
A common requirement is to group a list of records, each stored as a Map<String, Object>, by a specific attribute and then aggregate another attribute (e.g., sum, average, or count) within each group.
You have a list of products, each stored as a Map<String, Object>. You want to group them by category and calculate the average price per category.
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
public class MapAggregationExample {
public static void main(String[] args) {
List<Map<String, Object>> products = Arrays.asList(
createProduct("Laptop", "Electronics", 1200.00),
createProduct("Mouse", "Electronics", 25.00),
createProduct("Keyboard", "Electronics", 75.00),
createProduct("T-Shirt", "Apparel", 20.00),
createProduct("Jeans", "Apparel", 60.00),
createProduct("Book - Java", "Books", 45.00),
createProduct("Book - Design Patterns", "Books", 55.00)
);
Map<String, Double> averagePriceByCategory = products.stream()
.collect(Collectors.groupingBy(
product -> (String) product.get("category"),
Collectors.averagingDouble(product -> (Double) product.get("price"))
));
System.out.println("Average price by category:");
averagePriceByCategory.forEach((category, avgPrice) ->
System.out.printf(" %s: $%.2f%n", category, avgPrice));
}
private static Map<String, Object> createProduct(String name, String category, Double price) {
Map<String, Object> product = new java.util.HashMap<>();
product.put("name", name);
product.put("category", category);
product.put("price", price);
return product;
}
}Average price by category:
Electronics: $433.33
Apparel: $40.00
Books: $50.00We start with a list of product maps. Each map holds a category and a price.
Using Java’s Stream API, we group products by category and calculate the average price in each group using Collectors.averagingDouble.
This gives a Map<String, Double> with category names as keys and their average prices as values.
This approach is compact, readable, and practical for building reports or dashboards.
Casting (like (String) and (Double)) assumes that data is always clean and well-typed. That’s risky. In production code, use validation or, better yet — define a proper Product class instead of using Map<String, Double>.
Merging two maps is common in configuration management or API updates, but a simple putAllfails when maps are nested. A recursive merge is needed to combine nested structures intelligently.
Merge a base configuration map with an update map, where:
— Update map values override base map values for matching keys.
— Nested maps are merged recursively.
— Non-map values in the update map replace those in the base map.
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.Map;
@SuppressWarnings("unchecked")
public class DeepMergeMapsExample {
public static Map<String, Object> deepMerge(Map<String, Object> baseMap, Map<String, Object> updateMap) {
Map<String, Object> result = new LinkedHashMap<>(baseMap);
updateMap.forEach((key, updateValue) -> {
if (result.containsKey(key) && result.get(key) instanceof Map && updateValue instanceof Map) {
result.put(key, deepMerge((Map<String, Object>) result.get(key), (Map<String, Object>) updateValue));
} else {
result.put(key, updateValue);
}
});
return result;
}
public static void main(String[] args) {
Map<String, Object> baseConfig = new HashMap<>();
baseConfig.put("applicationName", "BaseApp");
baseConfig.put("version", "1.0");
Map<String, Object> baseDbSettings = new HashMap<>();
baseDbSettings.put("host", "localhost");
baseDbSettings.put("port", 5432);
baseDbSettings.put("username", "base_user");
baseConfig.put("database", baseDbSettings);
Map<String, Object> baseApiCalls = new HashMap<>();
baseApiCalls.put("timeout", 5000);
baseConfig.put("api", baseApiCalls);
Map<String, Object> updateConfig = new HashMap<>();
updateConfig.put("version", "1.1");
Map<String, Object> updateDbSettings = new HashMap<>();
updateDbSettings.put("port", 5433);
updateDbSettings.put("password", "updated_pass");
updateConfig.put("database", updateDbSettings);
updateConfig.put("logLevel", "DEBUG");
Map<String, Object> newFeatureSettings = new HashMap<>();
newFeatureSettings.put("enabled", true);
updateConfig.put("newFeature", newFeatureSettings);
System.out.println("Base Config: " + baseConfig);
System.out.println("Update Config: " + updateConfig);
Map<String, Object> mergedConfig = deepMerge(baseConfig, updateConfig);
System.out.println("\nMerged Config: " + mergedConfig);
}
}Merged Config: {
applicationName=BaseApp,
version=1.1,
database={host=localhost, port=5433, username=base_user, password=updated_pass},
api={timeout=5000},
logLevel=DEBUG,
newFeature={enabled=true}
}We start with two maps: base and update. We clone the base into a new LinkedHashMap (to preserve order).
For each key in the update if both base and update values are maps, we merge them recursively and if not, we overwrite with the update’s value.
This is perfect for merging nested configuration files or JSON responses.
You get a clean, recursive way to merge without mutating the original inputs. For extra safety in multithreaded environments, consider using ConcurrentHashMap, or make the maps immutable.
Flat maps with delimited keys (e.g., user.address.city) are common in configuration files or legacy APIs. Transforming these into nested maps is a powerful technique for restructuring data.
You’re working with a flat map where keys use dot-notation:
"user.address.city" → "Gotham"
You want to convert this into a proper nested map structure.
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.Map;
@SuppressWarnings("unchecked")
public class FlattenedToNestedMapExample {
public static Map<String, Object> unflatten(Map<String, Object> flatMap) {
Map<String, Object> nestedMap = new LinkedHashMap<>();
flatMap.forEach((flatKey, value) -> {
String[] keys = flatKey.split("\\.");
Map<String, Object> currentLevel = nestedMap;
for (int i = 0; i < keys.length - 1; i++) {
currentLevel = (Map<String, Object>) currentLevel.computeIfAbsent(keys[i], k -> new LinkedHashMap<>());
}
currentLevel.put(keys[keys.length - 1], value);
});
return nestedMap;
}
public static void main(String[] args) {
Map<String, Object> flatData = new LinkedHashMap<>();
flatData.put("user.name", "Bruce");
flatData.put("user.email", "[email protected]");
flatData.put("user.address.street", "4587 Main St");
flatData.put("user.address.city", "Gotham");
flatData.put("user.address.zip", "12345");
flatData.put("config.theme", "dark");
flatData.put("config.notifications.enabled", true);
System.out.println("Flat Data: " + flatData);
Map<String, Object> nestedData = unflatten(flatData);
System.out.println("\nNested Data: " + nestedData);
Map<String, Object> userMap = (Map<String, Object>) nestedData.get("user");
if (userMap != null) {
Map<String, Object> addressMap = (Map<String, Object>) userMap.get("address");
if (addressMap != null) {
System.out.println("\nUser City: " + addressMap.get("city")); // Gotham
}
}
}
}
Nested Data: {
user={name=Bruce, [email protected], address={street=4587 Main St, city=Gotham, zip=12345}},
config={theme=dark, notifications={enabled=true}}
}
User City: GothamEach key in the flat map, like "user.address.city", is split by dots into parts like ["user", "address", "city"]. These parts define a path in the nested structure.
The algorithm walks through this path, and at each level, it either moves deeper into the map or creates a new nested LinkedHashMap using computeIfAbsent. Once it reaches the final part of the path, it inserts the value.
This process reconstructs the full nested structure from a flattened key format, preserving hierarchy and readability.
If you’ve ever worked with .properties files or APIs that flatten JSON, you’ll run into this.
This method makes the data usable again, no manual get(get(get(...))) logic. It's clean, extendable, and avoids boilerplate.
Maps are powerful, especially when you treat them as dynamic data structures instead of just dumb key-value stores.
Whether you’re grouping records, merging configurations, or untangling flat keys into real structure, these patterns show how far you can go with Map<String, Object> and a bit of Stream API and recursion.
Lastly, use typed classes when possible, validate before casting, and wrap utilities like these in reusable methods or services.
If this article provided you with value, please consider buying me a coffee, only if you can afford it.
0
9
0