HeadRender
Fetches a skin, downscales it and turns each pixel into a HEX-coloured chat line — a real Minecraft head, in chat, next to whatever text you want. One static façade, async by default, with an in-memory LRU cache so repeated lookups are free.
| Group | com.github.aquatic-studios |
| Artifact | HeadRender |
| MC | 1.16+ (HEX colours required) |
| Java | 17+ |
| Platform | Bukkit / Spigot / Paper (any fork) |
| Dependencies | None |
Relocate com.aquaticstudios.headrender when shading. See
AquaLib → Shading for the exact Gradle and Maven blocks — the pattern
is identical.
Installation
repositories {
maven("https://jitpack.io")
}
dependencies {
implementation("com.github.aquatic-studios:HeadRender:1.2.0")
}Usage
import com.aquaticstudios.headrender.HeadRender;
HeadRender.render(player.getUniqueId())
.thenAccept(lines -> lines.forEach(player::sendMessage));With text to the right of the head:
HeadRender.builder(player.getUniqueId())
.resolution(8)
.symbol("█")
.lines(
"&ecff&lSenkex",
"&7Rank: �a84ffVIP",
"&7Joined: &f2 minutes ago"
)
.render()
.thenAccept(lines -> lines.forEach(Bukkit::broadcast));Options
| Option | Default | Notes |
|---|---|---|
resolution(int) | 8 | 8, 16 or 32. One chat line per row |
symbol(String) | "█" | Any single character |
overlay(boolean) | true | Render the hat layer on top |
lines(String…) | none | Text placed to the right, top-aligned |
fallback(Skin) | STEVE | Used when the skin cannot be fetched |
Caching
Skins are cached in memory using an LRU map, keyed by UUID.
HeadRender.cache().maxSize(500);
HeadRender.cache().expireAfter(Duration.ofMinutes(60));
HeadRender.cache().invalidate(uuid);Mojang rate-limits the session server. With caching disabled, a server rendering a head on every
join will start receiving 429 Too Many Requests and fall back to Steve.
Threading
render() always returns a CompletableFuture. Fetch and downscale happen on a shared pool, so the
future completes off the main thread.
HeadRender.render(uuid).thenAccept(lines ->
Bukkit.getScheduler().runTask(plugin, () ->
lines.forEach(player::sendMessage)
)
);Never call .join() or .get() from the main thread. A cold lookup takes 100–800 ms and the whole
server stops for that long.