Other answers haw a flow that intermediate string is being created. Except accepted answer, but it asumes that we don't already have StringBuilder. And yes we can use Formatter INSTEAD of SB. But in many cases in code we receive SB from outside, so we can wrap it into formatter.
Correct answer that have full correspondence to provided C# code is:
// sb.AppendFormat("\\u{0:X04}", i);
Formatter fmt = new Formatter(sb);
fmt.format("\\u%04x", i);
Data would be appended to end of string builder directly. No intermediate string is created. You can also specify locale if needed.
Later applies if we feed to Formatter not String builder, but arbitrary Appendable, like text file.
We don't need to mange lifetime of formatter directly( close it), if we would manage lifetime of writer (if that's not SB but file). If we pass in Writter and don't use that variable anymore -- we would need to close() Fromatter.
append(string format, Object.. args)overload (or perhaps namedappendFormat), but there's not.