Skip to Content
LibrariesHeadRender

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.

Groupcom.github.aquatic-studios
ArtifactHeadRender
MC1.16+ (HEX colours required)
Java17+
PlatformBukkit / Spigot / Paper (any fork)
DependenciesNone

Relocate com.aquaticstudios.headrender when shading. See AquaLib → Shading for the exact Gradle and Maven blocks — the pattern is identical.

Installation

build.gradle.kts
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( "&#38ecff&lSenkex", "&7Rank: &#0a84ffVIP", "&7Joined: &f2 minutes ago" ) .render() .thenAccept(lines -> lines.forEach(Bukkit::broadcast));

Options

OptionDefaultNotes
resolution(int)88, 16 or 32. One chat line per row
symbol(String)"█"Any single character
overlay(boolean)trueRender the hat layer on top
lines(String…)noneText placed to the right, top-aligned
fallback(Skin)STEVEUsed 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.

Last updated on