System Prompt Builder — Java source
Assemble a system prompt from ordered blocks — role, context, constraints, output format — with a live token count, soft-limit warnings, and a shareable URL. 100% client-side.
This is the Java implementation — the same logic the interactive tool runs, in a shareable, citable form.
// System Prompt Builder — assemble an ordered list of prompt blocks into a
// markdown-structured system prompt, with pure list operations, presets,
// warnings, and a compact URL codec for shareable state.
//
// Language: Java (17+, zero dependencies; java.util.Base64 + a tiny JSON
// triple encoder keep it stdlib-only)
// Port of src/lib/systemPromptBuilder.ts (the canonical TypeScript
// implementation). Field names stay camelCase to match the TS surface.
// Tool page: https://dev.cosmolabs.org/tools/system-prompt-builder
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Base64;
import java.util.List;
import java.util.Locale;
public final class SystemPromptBuilder {
/** Blocks whose assembled size starts crowding the context on most models. */
public static final long SYSTEM_PROMPT_SOFT_LIMIT_TOKENS = 2000;
public record PromptBlock(String id, String title, String content, boolean enabled) {}
public record PromptPreset(String id, String title, String description, String content) {}
public record PromptReport(String assembled, long tokens, List<String> warnings) {}
/** Ordered starter templates — the recommended skeleton of a system prompt. */
public static final List<PromptPreset> SYSTEM_PROMPT_PRESETS = List.of(
new PromptPreset("role", "Role", "Who the model is and what it optimizes for.",
"You are a senior software engineer. You give correct, concise answers "
+ "and say so plainly when you are unsure."),
new PromptPreset("context", "Context", "The situation the model is working in.",
"The user is a developer working in a TypeScript codebase. Prefer "
+ "runnable examples over prose when both work."),
new PromptPreset("constraints", "Constraints", "Hard rules the model must not break.",
"- Never invent library APIs; use only the ones in the provided code.\n"
+ "- Keep answers under 300 words unless asked for more."),
new PromptPreset("output-format", "Output format", "The exact shape of the answer.",
"Respond with: 1) a one-line summary, 2) a fenced code block, "
+ "3) any caveats as bullet points."),
new PromptPreset("examples", "Examples",
"Few-shot demonstrations of the desired behavior.",
"Input: reverse \"abc\"\nOutput: \"cba\""),
new PromptPreset("tone", "Tone", "Voice and register.",
"Direct and friendly. No filler openers, no apologies."),
new PromptPreset("refusal", "Refusal policy",
"How to handle out-of-scope requests.",
"If a request is outside your scope, say so in one sentence and suggest "
+ "the closest thing you can do."),
new PromptPreset("safety", "Safety", "Guardrails for sensitive content.",
"Refuse requests that could cause harm, and never echo secrets, keys, "
+ "or credentials back in full."));
/** Render enabled, non-empty blocks (in order) as one markdown prompt. */
public static String assemblePrompt(List<PromptBlock> blocks, boolean headers) {
List<String> parts = new ArrayList<>();
for (PromptBlock b : blocks) {
String content = b.content() == null ? "" : b.content().trim();
if (!b.enabled() || content.isEmpty()) continue;
if (headers) {
String title = b.title() == null || b.title().trim().isEmpty()
? "Untitled" : b.title().trim();
parts.add("## " + title + "\n" + content);
} else {
parts.add(content);
}
}
return String.join("\n\n", parts).trim();
}
/** Append a block (caller supplies the id so the lib stays pure). */
public static List<PromptBlock> addBlock(List<PromptBlock> blocks, String id,
String title, String content, boolean enabled) {
List<PromptBlock> next = new ArrayList<>(blocks);
next.add(new PromptBlock(id, title, content, enabled));
return next;
}
/** Patch one block by id; unknown ids leave the list unchanged. */
public static List<PromptBlock> updateBlock(List<PromptBlock> blocks, String id,
String title, String content, Boolean enabled) {
List<PromptBlock> next = new ArrayList<>();
for (PromptBlock b : blocks) {
if (b.id().equals(id)) {
next.add(new PromptBlock(b.id(),
title != null ? title : b.title(),
content != null ? content : b.content(),
enabled != null ? enabled : b.enabled()));
} else {
next.add(b);
}
}
return next;
}
/** Flip one block's enabled flag by id. */
public static List<PromptBlock> toggleBlock(List<PromptBlock> blocks, String id) {
List<PromptBlock> next = new ArrayList<>();
for (PromptBlock b : blocks) {
next.add(b.id().equals(id)
? new PromptBlock(b.id(), b.title(), b.content(), !b.enabled())
: b);
}
return next;
}
/** Remove one block by id. */
public static List<PromptBlock> removeBlock(List<PromptBlock> blocks, String id) {
List<PromptBlock> next = new ArrayList<>();
for (PromptBlock b : blocks) {
if (!b.id().equals(id)) next.add(b);
}
return next;
}
/** Move a block (clamped; no-op when indexes are out of range or equal). */
public static List<PromptBlock> moveBlock(List<PromptBlock> blocks, int from, int to) {
if (from < 0 || from >= blocks.size() || to < 0 || to >= blocks.size() || from == to) {
return new ArrayList<>(blocks);
}
List<PromptBlock> next = new ArrayList<>(blocks);
PromptBlock moved = next.remove(from);
next.add(to, moved);
return next;
}
/** Assemble + count + lint in one pass — the island's live report. */
public static PromptReport buildReport(List<PromptBlock> blocks) {
String assembled = assemblePrompt(blocks, true);
long tokens = assembled.isEmpty() ? 0 : estimateTokens(assembled);
List<String> warnings = new ArrayList<>();
if (tokens > SYSTEM_PROMPT_SOFT_LIMIT_TOKENS) {
warnings.add(String.format(Locale.US,
"Assembled prompt is ~%,d tokens — beyond %,d it starts crowding the "
+ "context window on most models.",
tokens, SYSTEM_PROMPT_SOFT_LIMIT_TOKENS));
}
boolean hasRole = false;
for (PromptBlock b : blocks) {
if (b.enabled() && b.title() != null
&& b.title().trim().toLowerCase(Locale.ROOT).equals("role")) {
hasRole = true;
break;
}
}
if (!blocks.isEmpty() && !hasRole) {
warnings.add("No enabled \"Role\" block — stating who the model is tends to "
+ "anchor every following instruction.");
}
if (!blocks.isEmpty() && assembled.isEmpty()) {
warnings.add("Every block is disabled or empty — the assembled prompt is empty.");
}
return new PromptReport(assembled, tokens, warnings);
}
// ---- shareable state codec (URL-safe, compact) -----------------------
// Triples of [enabled(0/1), title, content] keep URLs far smaller than the
// full object shape; ids are regenerated on decode (they are UI-local).
private static final int MAX_ENCODED_LENGTH = 4000;
private static String toBase64Url(String s) {
return Base64.getUrlEncoder().withoutPadding()
.encodeToString(s.getBytes(StandardCharsets.UTF_8));
}
private static String fromBase64Url(String s) {
byte[] bytes = Base64.getUrlDecoder().decode(s);
return new String(bytes, StandardCharsets.UTF_8);
}
/** Minimal JSON string literal escape for the compact codec. */
private static String jsonEscape(String s) {
StringBuilder out = new StringBuilder();
for (int i = 0; i < s.length(); i++) {
char c = s.charAt(i);
switch (c) {
case '"' -> out.append("\\\"");
case '\\' -> out.append("\\\\");
case '\n' -> out.append("\\n");
case '\r' -> out.append("\\r");
case '\t' -> out.append("\\t");
default -> {
if (c < 0x20) {
out.append(String.format(Locale.ROOT, "\\u%04x", (int) c));
} else {
out.append(c);
}
}
}
}
return out.toString();
}
/** Decode a JSON string literal produced by {@link #jsonEscape}. */
private static String jsonUnescape(String s) {
StringBuilder out = new StringBuilder();
for (int i = 0; i < s.length(); i++) {
char c = s.charAt(i);
if (c == '\\' && i + 1 < s.length()) {
char e = s.charAt(++i);
switch (e) {
case 'n' -> out.append('\n');
case 'r' -> out.append('\r');
case 't' -> out.append('\t');
case 'u' -> {
if (i + 4 < s.length()) {
out.append((char) Integer.parseInt(s.substring(i + 1, i + 5), 16));
i += 4;
}
}
default -> out.append(e);
}
} else {
out.append(c);
}
}
return out.toString();
}
/** Encode blocks to a compact base64url string; "" when blocks are empty. */
public static String encodeBlocks(List<PromptBlock> blocks) {
if (blocks.isEmpty()) return "";
StringBuilder json = new StringBuilder("[");
for (int i = 0; i < blocks.size(); i++) {
PromptBlock b = blocks.get(i);
if (i > 0) json.append(',');
json.append('[').append(b.enabled() ? 1 : 0)
.append(",\"").append(jsonEscape(b.title()))
.append("\",\"").append(jsonEscape(b.content()))
.append("\"]");
}
json.append(']');
return toBase64Url(json.toString());
}
/** True when the encoded form would make an uncomfortably long URL. */
public static boolean encodedTooLong(String encoded) {
return encoded.length() > MAX_ENCODED_LENGTH;
}
/**
* Decode {@link #encodeBlocks} output; regenerates ids (b1, b2, …).
* Returns null on malformed input — never throws.
*/
public static List<PromptBlock> decodeBlocks(String encoded) {
if (encoded.isEmpty()) return new ArrayList<>();
try {
String json = fromBase64Url(encoded);
if (json.charAt(0) != '[' || json.charAt(json.length() - 1) != ']') return null;
String body = json.substring(1, json.length() - 1);
List<PromptBlock> blocks = new ArrayList<>();
int i = 0;
while (i < body.length()) {
if (body.charAt(i) == ',') i++;
if (i >= body.length() || body.charAt(i) != '[') return null;
i++; // consume '['
if (i >= body.length() || !Character.isDigit(body.charAt(i))) return null;
int enabledStart = i;
while (Character.isDigit(body.charAt(i))) i++;
int enabled = Integer.parseInt(body.substring(enabledStart, i));
String[] strings = new String[2];
for (int s = 0; s < 2; s++) {
if (body.charAt(i) != ',') return null;
i++;
if (body.charAt(i) != '"') return null;
i++;
StringBuilder lit = new StringBuilder();
while (body.charAt(i) != '"') {
if (body.charAt(i) == '\\') {
lit.append(body.charAt(i)).append(body.charAt(i + 1));
i += 2;
} else {
lit.append(body.charAt(i));
i++;
}
}
i++; // closing quote
strings[s] = jsonUnescape(lit.toString());
}
if (i >= body.length() || body.charAt(i) != ']') return null;
i++; // consume ']'
blocks.add(new PromptBlock(
"b" + (blocks.size() + 1), strings[0], strings[1], enabled == 1));
}
return blocks;
} catch (RuntimeException e) {
return null;
}
}
/**
* The prose path of the tokenEstimator, inlined: every non-empty line
* costs max(1, round(length / 4)) tokens; empty text is 0.
*/
private static long estimateTokens(String text) {
if (text.isEmpty()) return 0;
long tokens = 0;
for (String line : text.split("\n", -1)) {
if (!line.isEmpty()) {
tokens += Math.max(1, Math.round(line.length() / 4.0));
}
}
return tokens;
}
private SystemPromptBuilder() {}
}
Also available in 13 other languages
Every CosmoDev tool ships its pure logic in TypeScript (web) and Go (CLI), with authored implementations in a dozen-plus languages — the same contract, ported. Compare all languages side by side →