Unfortunately, the other two Answers both fail with most characters.
Avoid legacy type char
The char type is legacy, essentially broken since Java 2, legacy since Java 5. As a 16-bit value, char is physically incapable of representing most of the 144,697 characters defined in Unicode.
See one Answer’s code break:
String input = "😷😷abbccd";
String output =
input
.chars()
.mapToObj( s -> Character.toLowerCase( ( char ) s ) ) // notice here Character.valueOf was redundant, we're already dealing with a char
.collect( Collectors.groupingBy( Function.identity() , LinkedHashMap :: new , Collectors.counting() ) )
.entrySet().stream()
.map( n -> n.getKey() + "" + ( n.getValue() == 1 ? "" : n.getValue() ) )
.collect( Collectors.joining() );
System.out.println( "output = " + output );
output = ?2?2ab2c2d
Code point
Use code point integer numbers instead, when working with individual characters. A code point is the number permanently assigned to each character in Unicode. They range from zero to just over a million.
You will find code point related method scattered around the Java classes. These include String, StringBuilder, Character, etc.
The String#codePoints method returns an IntStream of code points, the code point number for each character in the string.
Here is a re-worked version of the clever code from Answer by Federico klez Culloca. Kudos to him, as I could not have come up with that approach.
String input = "😷😷abbccd";
String output =
input
.codePoints()
.map( Character :: toLowerCase )
.mapToObj( codePoint -> Character.toString( codePoint ) )
.collect( Collectors.groupingBy( Function.identity() , LinkedHashMap :: new , Collectors.counting() ) )
.entrySet().stream()
.map( n -> n.getKey() + "" + ( n.getValue() == 1 ? "" : n.getValue() ) )
.collect( Collectors.joining() );
System.out.println( "output = " + output );
output = 😷2ab2c2d