Pattern.compile("\\(([^)]+)\\)"); The Pattern is always the same so you may just as well make it a `private static final` constant. This will avoid having to recompile the pattern each time. --- Arrays.asList(/*..*/).stream().collect(Collectors.toList()); Why this dance? Arrays.asList returns a List. You only do a stream on the return result. final List<String> list = Arrays.asList(matcher.group(1).split(",")); list.stream().forEach(a -> { final String[] ar = a.split("->"); map.put(ar[0], ar[1]); }); However a Pattern an return a Stream<String> split according to the pattern directly: Pattern COMMA_PATTERN = Pattern.compile("\\s*,\\s*"); COMMA_PATTERN.splitAsStream(matcher.group(3)).forEach(a -> { //... } --- You can use a single match of a Pattern to find both the leading values and the string inside the parenthesis: private static final Pattern FULL_PATTERN = Pattern.compile("^(\\$\\w+)\\s*=>\\s*(\\w+)\\s*\\(([^)]+)\\)$"); private static final Pattern KEY_VALUE_PATTERN = Pattern.compile("(\\w+)\\s*->\\s*([\\$\\w]+)"); private static final Pattern COMMA_PATTERN = Pattern.compile("\\s*,\\s*"); public static Macro getMacro(String str) { final Macro macro = new Macro(); final Map<String, String> map = new HashMap<String, String>(); final Matcher matcher = FULL_PATTERN.matcher(str); if(!matcher.matches()) { throw new IllegalArgumentException("..."); } COMMA_PATTERN.splitAsStream(matcher.group(3)).forEach(a -> { final Matcher kvMatcher = KEY_VALUE_PATTERN.matcher(a); if(!kvMatcher.matches()){ throw new IllegalArgumentException("..."); } map.put(kvMatcher.group(1), kvMatcher.group(2)); }); macro.setMap(map); macro.setKey(matcher.group(1)); macro.setType(matcher.group(2)); return macro; } You'll note I added `\\s*` to various points in each pattern, This lets you ignore the whitespace that may surround the operators. The `\\w` in a Pattern means a word character. If you only want alphanumeric +undescore then you'llneed to replace each instance of it with `[\\p{Alnum}_]`