code-playpad

Java

language="java" compiles and runs a documented subset of Java 25 in the reader's browser. There is no JVM and no server: an engine written for code-playpad type-checks the program, compiles it, and interprets it in a Web Worker. It is one ~39KB (gzipped) chunk, and pages without a Java widget never download it.

Hello, compact source file

Java 25 lets a small program be just methods and fields: no class declaration, and a main that is neither public nor static. IO.println prints anything. Fields keep their values across method calls, because the launcher creates one instance and calls main on it.

int visits = 0; void main() { greet("Ada"); greet("Grace"); IO.println("greeted " + visits + " people"); } void greet(String name) { visits++; IO.println("Hello, " + name + "!"); }

Or a classic class

The familiar form works too: a class with public static void main(String[] args), static methods, and arrays. Integer arithmetic is real 32-bit Java arithmetic, so (low + high) >>> 1 does exactly what it does on a JVM.

public class Main { // Returns the index of target in sorted, or -1 when it is absent. static int binarySearch(int[] sorted, int target) { int low = 0; int high = sorted.length - 1; while (low <= high) { int mid = (low + high) >>> 1; if (sorted[mid] == target) return mid; if (sorted[mid] < target) low = mid + 1; else high = mid - 1; } return -1; } public static void main(String[] args) { int[] primes = {2, 3, 5, 7, 11, 13, 17, 19, 23, 29}; System.out.println("index of 13: " + binarySearch(primes, 13)); System.out.println("index of 4: " + binarySearch(primes, 4)); } }

Methods, recursion and overloading

Calls are resolved the way javac resolves them, overloads included: describe('x') picks the char version and describe('x' + 1) the int one. Recursion is real, and so is overflow: 13! does not fit in an int.

static int factorial(int n) { if (n <= 1) return 1; return n * factorial(n - 1); } static String describe(int x) { return "int " + x; } static String describe(char c) { return "char " + c; } void main() { for (int n = 10; n <= 13; n++) { IO.println(n + "! = " + factorial(n)); } IO.println(describe('x')); IO.println(describe('x' + 1)); }

Strings, characters and arrays

A char is a number you can count with (c - 'a'), and Strings index by UTF-16 code unit, as in Java. == compares references, so use equals for content.

void main() { String text = "hello world"; int[] counts = new int[26]; for (int i = 0; i < text.length(); i++) { char c = text.charAt(i); if (c >= 'a' && c <= 'z') counts[c - 'a']++; } for (char c = 'a'; c <= 'z'; c++) { if (counts[c - 'a'] > 1) IO.println(c + " appears " + counts[c - 'a'] + " times"); } String built = "hel"; built += "lo"; IO.println(built == "hello"); // false: a different object IO.println(built.equals("hello")); // true: same characters }

Numbers and switch

long and double behave exactly as on a JVM, down to how a double prints (0.30000000000000004) and how an int overflows. A switch can be a statement or, as here, an expression with several labels per case; arrays can have more dimensions, and enhanced for walks them.

void main() { long population = 8_100_000_000L; double growth = 0.0088; for (int year = 2026; year <= 2028; year++) { population += (long) (population * growth); IO.println(year + ": " + population); } IO.println(0.1 + 0.2); IO.println(Integer.MAX_VALUE + 1); IO.println(Long.MAX_VALUE / 7 + " " + 22.0 / 7); int[][] scores = {{90, 72}, {65, 88}, {100, 41}}; for (int[] pair : scores) { int best = Math.max(pair[0], pair[1]); String grade = switch (best / 10) { case 10, 9 -> "A"; case 8 -> "B"; case 7 -> "C"; default -> "below C"; }; IO.println(best + " -> " + grade); } }

Objects and classes

Declare classes with fields, constructors and methods, create them with new, and link them together. A toString() you write is what IO.println and + use. In a compact source file, classes like these are members of the file's implicit class, so they can also use its fields.

void main() { Stack stack = new Stack(); for (int n = 1; n <= 3; n++) stack.push(n * n); IO.println(stack); IO.println("popped " + stack.pop() + ", now " + stack); IO.println(stack.isEmpty() ? "empty" : "top is " + stack.top.value); } class Stack { Node top; int size; void push(int value) { top = new Node(value, top); size++; } int pop() { int value = top.value; top = top.next; size--; return value; } boolean isEmpty() { return top == null; } @Override public String toString() { String text = ""; for (Node n = top; n != null; n = n.next) { text += (text.isEmpty() ? "" : ", ") + n.value; } return "[" + text + "] (" + size + ")"; } } class Node { final int value; final Node next; Node(int value, Node next) { this.value = value; this.next = next; } }

Collections and StringBuilder

ArrayList, HashMap, HashSet, ArrayDeque and PriorityQueue, with generics, the diamond and boxing, plus StringBuilder, Character and Arrays. The library is a separate download, made only the first time a program on the page uses it. A HashMap or HashSet prints in the JDK's order (the engine lays out its table as the JDK does).

import java.util.*; public class Main { public static void main(String[] args) { String[] words = {"the", "quick", "brown", "fox", "jumps", "over", "the", "lazy", "dog", "the", "end"}; Map<String, Integer> counts = new HashMap<>(); List<String> order = new ArrayList<>(); for (String word : words) { if (!counts.containsKey(word)) order.add(word); counts.put(word, counts.getOrDefault(word, 0) + 1); } System.out.println(order.size() + " distinct words, \"the\" x" + counts.get("the")); PriorityQueue<Integer> lengths = new PriorityQueue<>(); for (String word : order) lengths.add(word.length()); StringBuilder sb = new StringBuilder(); while (!lengths.isEmpty()) sb.append(sb.isEmpty() ? "" : " ").append(lengths.poll()); System.out.println("lengths: " + sb); Deque<Character> stack = new ArrayDeque<>(); for (char c : "stressed".toCharArray()) stack.push(Character.toUpperCase(c)); StringBuilder reversed = new StringBuilder(); while (!stack.isEmpty()) reversed.append(stack.pop()); System.out.println(reversed + " " + Arrays.toString(new int[] {3, 1, 2})); } }

Sorting with comparator lambdas

A (a, b) -> … lambda orders an Arrays.sort, a List.sort or a PriorityQueue. Sorting works as on a JVM down to the details: the classic overflow bug of (a, b) -> a - b puts the same elements in the same wrong order, and an exception inside a comparator shows the lambda's own frame.

import java.util.*; public class Main { public static void main(String[] args) { int[][] intervals = {{8, 10}, {1, 3}, {2, 6}, {15, 18}}; Arrays.sort(intervals, (a, b) -> Integer.compare(a[0], b[0])); System.out.println(Arrays.deepToString(intervals)); PriorityQueue<String> shortestFirst = new PriorityQueue<>((a, b) -> { if (a.length() != b.length()) return a.length() - b.length(); return a.compareTo(b); }); for (String w : new String[] {"pear", "fig", "apple", "kiwi"}) shortestFirst.add(w); StringBuilder order = new StringBuilder(); while (!shortestFirst.isEmpty()) order.append(shortestFirst.poll()).append(' '); System.out.println(order); Integer[] xs = {Integer.MIN_VALUE, 5, -3, Integer.MAX_VALUE}; Arrays.sort(xs, (a, b) -> a - b); System.out.println("overflowing comparator: " + Arrays.toString(xs)); } }

Text, formatting and random numbers

String's everyday methods, Java's regex syntax in split and replaceAll, and printf with Java's own rounding: %.2f of 1.005 is 1.01, where C would print 1.00. A seeded Random gives exactly the numbers a JVM gives.

import java.util.Random; public class Main { public static void main(String[] args) { String line = " Ada Lovelace, 1815; Alan Turing, 1912 "; for (String entry : line.strip().split(";\\s*")) { String[] parts = entry.split(",\\s*"); String name = parts[0].replaceAll("(\\w+) (\\w+)", "$2, $1"); System.out.printf("%-16s|%5d|%n", name, Integer.parseInt(parts[1])); } System.out.println(String.format("%.2f %.2f %,d", 1.005, 2.675, 1234567)); System.out.println("ab".repeat(3) + " " + "Mississippi".lastIndexOf("ss") + " " + String.join("-", "a", "b", "c")); Random dice = new Random(42); StringBuilder rolls = new StringBuilder(); for (int i = 0; i < 10; i++) rolls.append(dice.nextInt(6) + 1).append(' '); System.out.println(rolls + "| " + Math.floorMod(-7, 3) + " " + Math.pow(2, 0.5)); } }

Reading input

Scanner and IO.readln read the input box, all at once, as if it were piped to java. Try the classic trap: after nextInt(), a nextLine() returns the rest of that line, which is empty. Clear the box and run again to see what happens when the input runs out.

import java.util.Scanner; void main() { Scanner in = new Scanner(System.in); int n = in.nextInt(); String rest = in.nextLine(); IO.println("n = " + n + ", rest of the line = [" + rest + "]"); int oldest = 0; String name = ""; for (int i = 0; i < n; i++) { String who = in.next(); int age = in.nextInt(); if (age > oldest) { oldest = age; name = who; } } IO.println("oldest: " + name + " (" + oldest + ")"); }

Classes, records, enums and exceptions

Interfaces, records (with a compact constructor that validates), an enum, instanceof patterns, and a checked exception of the program's own, caught with finally. A record's toString(), the exception's class name and the %.2f rounding are the JDK's.

import java.util.*; interface Shape { double area(); } record Circle(double r) implements Shape { public double area() { return Math.PI * r * r; } } record Rect(double w, double h) implements Shape { Rect { if (w <= 0 || h <= 0) throw new IllegalArgumentException("bad size: " + w + " x " + h); } public double area() { return w * h; } } enum Unit { CM, M } class NotFound extends Exception { NotFound(String what) { super(what + " not found"); } } Shape find(List<Shape> shapes, String kind) throws NotFound { for (Shape s : shapes) { if (s instanceof Circle c && kind.equals("circle")) return c; if (s instanceof Rect r && kind.equals("rect")) return r; } throw new NotFound(kind); } void main() { List<Shape> shapes = new ArrayList<>(); shapes.add(new Circle(1)); shapes.add(new Rect(2, 3)); try { shapes.add(new Rect(-1, 2)); } catch (IllegalArgumentException e) { IO.println("skipped: " + e.getMessage()); } for (Shape s : shapes) IO.println(s + String.format(" area %.2f %s", s.area(), Unit.CM)); try { IO.println(find(shapes, "rect")); IO.println(find(shapes, "triangle")); } catch (NotFound e) { IO.println(e); } finally { IO.println("done"); } }

Collections, lambdas and generics

merge with Integer::sum, a comparator chain, a generic record and a generic trie with an inner node class, var, and a graph as List<Integer>[]. As from the JDK's source launcher, javac's [unchecked] warning for new ArrayList[3] comes first on stderr; @SuppressWarnings("unchecked") on main would silence it.

import java.util.*; public class Main { record Pair<A, B>(A first, B second) { } static class Trie<V> { class Node { Map<Character, Node> next = new TreeMap<>(); V value; } Node root = new Node(); void put(String key, V value) { Node n = root; for (char c : key.toCharArray()) n = n.next.computeIfAbsent(c, k -> new Node()); n.value = value; } V get(String key) { Node n = root; for (char c : key.toCharArray()) if ((n = n.next.get(c)) == null) return null; return n.value; } } static <T extends Comparable<T>> T max(Collection<T> xs) { T best = null; for (T x : xs) if (best == null || x.compareTo(best) > 0) best = x; return best; } public static void main(String[] args) { String text = "the cat and the dog and the bird"; Map<String, Integer> counts = new TreeMap<>(); for (String w : text.split(" ")) counts.merge(w, 1, Integer::sum); System.out.println(counts + " longest: " + max(counts.keySet())); List<Pair<String, Integer>> pairs = new ArrayList<>(); counts.forEach((w, n) -> pairs.add(new Pair<>(w, n))); pairs.sort(Comparator.comparing((Pair<String, Integer> p) -> p.second()).reversed().thenComparing(Pair::first)); System.out.println(pairs.get(0) + " " + pairs.get(1)); var trie = new Trie<Integer>(); trie.put("cat", 1); trie.put("car", 2); System.out.println(trie.get("car") + " " + trie.get("ca")); List<Integer>[] graph = new ArrayList[3]; for (int i = 0; i < graph.length; i++) graph[i] = new ArrayList<>(); graph[0].add(1); graph[1].add(2); System.out.println(Arrays.toString(graph)); } }

Anonymous classes and streams

An anonymous Comparator (the JDK names it Main$1), and stream pipelines with Collectors. A pipeline runs element by element and stops early as the JDK's does: the peek below prints three squares, not five, because limit(3) has enough.

import java.util.*; import java.util.stream.*; public class Main { public static void main(String[] args) { int[] nums = {4, 1, 4, 3, 1, 4}; Map<Integer, Long> freq = Arrays.stream(nums).boxed().collect(Collectors.groupingBy(n -> n, Collectors.counting())); Comparator<Integer> byFrequency = new Comparator<>() { public int compare(Integer a, Integer b) { return Long.compare(freq.get(b), freq.get(a)); } }; PriorityQueue<Integer> pq = new PriorityQueue<>(byFrequency); pq.addAll(freq.keySet()); System.out.println(freq + " top " + pq.peek() + " " + pq.getClass().getName() + " " + byFrequency.getClass().getName()); List<String> words = List.of("stream", "lambda", "class", "java"); System.out.println(words.stream().filter(w -> w.length() > 4).map(String::toUpperCase).sorted().collect(Collectors.joining(", ", "[", "]"))); System.out.println(IntStream.rangeClosed(1, 5).map(i -> i * i).peek(i -> System.out.print(i + " ")).limit(3).sum()); } }

Local classes, subList and raw types

A record and a class declared inside main get javac's names (Main$1Circle); subList is a view, so sorting and clearing it changes the list behind it; and a raw List compiles with javac's [unchecked] warnings, which arrive on stderr before the output.

import java.util.*; public class Main { interface Shape { double area(); } public static void main(String[] args) { record Sq(double s) implements Shape { public double area() { return s * s; } } class Circle implements Shape { final double r; Circle(double r) { this.r = r; } public double area() { return 3 * r * r; } public String toString() { return "Circle(" + r + ")"; } } List<Shape> shapes = new ArrayList<>(List.of(new Sq(2), new Circle(1), new Sq(1), new Circle(2))); List<Shape> window = shapes.subList(1, 3); window.sort(Comparator.comparingDouble(Shape::area)); System.out.println(shapes + " " + window.getClass().getName() + " " + new Circle(0).getClass().getName()); window.clear(); System.out.println(shapes); List legacy = new ArrayList(); legacy.add("old"); List<String> typed = legacy; System.out.println(typed.get(0).length()); } }

Sealed types and pattern matching

A sealed interface of records, taken apart by record patterns in a switch that needs no default because it covers every permitted subclass. Guards (when), case null, and float and byte arithmetic print as on JDK 25.

import java.util.*; public class Main { sealed interface Shape permits Circle, Square, Group { } record Circle(float r) implements Shape { } record Square(float side) implements Shape { } record Group(String name, List<Shape> parts) implements Shape { } static float area(Shape s) { return switch (s) { case Circle(float r) -> 3.14159f * r * r; case Square(var side) -> side * side; case Group(String name, var parts) -> { float total = 0; for (Shape p : parts) total += area(p); yield total; } }; } static String describe(Object o) { return switch (o) { case null -> "nothing"; case Shape s when area(s) > 10 -> "a big shape"; case Shape s -> "a shape of area " + area(s); case Byte b -> "the byte " + b; default -> "something else"; }; } public static void main(String[] args) { Shape g = new Group("pair", List.of(new Circle(1.5f), new Square(0.1f))); System.out.println(area(g)); for (Object o : new Object[] {g, new Square(2), (byte) -128, null, 0.1f}) System.out.println(describe(o)); byte b = 120; b += 10; short s = (short) 70000; System.out.println(b + " " + s + " " + (float) 0.1 + " " + 1.1f * 3); } }

BigDecimal, BigInteger, EnumMap and EnumSet

Money in BigDecimal (exact, unlike 0.1 + 0.2), prices kept per enum constant in an EnumMap, and a 40-digit factorial in BigInteger, all printed digit for digit as JDK 25 prints them.

import java.math.*; import java.util.*; public class Main { enum Item { COFFEE, BAGEL, JUICE } public static void main(String[] args) { Map<Item, BigDecimal> prices = new EnumMap<>(Item.class); prices.put(Item.JUICE, new BigDecimal("3.75")); prices.put(Item.COFFEE, new BigDecimal("2.50")); prices.put(Item.BAGEL, new BigDecimal("1.99")); BigDecimal subtotal = BigDecimal.ZERO; for (BigDecimal p : prices.values()) subtotal = subtotal.add(p.multiply(BigDecimal.valueOf(3))); BigDecimal tax = subtotal.multiply(new BigDecimal("0.0825")).setScale(2, RoundingMode.HALF_UP); System.out.println(prices + " subtotal " + subtotal + " tax " + tax + " total " + subtotal.add(tax)); System.out.println(BigDecimal.ONE.divide(new BigDecimal("3"), 20, RoundingMode.HALF_EVEN) + " " + (0.1 + 0.2) + " vs " + new BigDecimal("0.1").add(new BigDecimal("0.2"))); EnumSet<Item> breakfast = EnumSet.of(Item.BAGEL, Item.COFFEE); System.out.println(breakfast + " " + EnumSet.complementOf(breakfast)); BigInteger f = BigInteger.ONE; for (int i = 2; i <= 40; i++) f = f.multiply(BigInteger.valueOf(i)); System.out.println("40! = " + f + " mod 1e9+7 = " + f.mod(BigInteger.valueOf(1_000_000_007))); } }

Fast input and output, and static imports

The competitive programmer's template: BufferedReader and StringTokenizer read the input box, and PrintWriter buffers the output. The second PrintWriter is never flushed, so, as on the JDK, its line never appears. Remove out.flush() to see the first one vanish too. import static brings in max, abs and sqrt.

import java.io.*; import java.util.*; import static java.lang.Math.*; public class Main { public static void main(String[] args) throws IOException { BufferedReader br = new BufferedReader(new InputStreamReader(System.in)); int n = Integer.parseInt(br.readLine().trim()); StringTokenizer st = new StringTokenizer(br.readLine()); PrintWriter out = new PrintWriter(new BufferedWriter(new OutputStreamWriter(System.out))); long sum = 0, largest = 0; for (int i = 0; i < n; i++) { long x = Long.parseLong(st.nextToken()); sum += x; largest = max(largest, abs(x)); } out.printf("sum %d, largest magnitude %d, sqrt %.3f%n", sum, largest, sqrt(largest)); out.flush(); PrintWriter lost = new PrintWriter(System.out); lost.println("never flushed, so never printed"); System.out.println("done"); } }

Initializer blocks and text blocks

A static block fills an enum's lookup map once, an instance block runs before every constructor's body, and a text block keeps a multi-line template readable: its indentation up to the closing """ is stripped, as javac strips it.

import java.util.*; public class Main { enum Op { ADD('+'), SUB('-'), MUL('*'); private static final Map<Character, Op> BY_SYMBOL = new HashMap<>(); static { for (Op op : values()) BY_SYMBOL.put(op.symbol, op); } final char symbol; Op(char symbol) { this.symbol = symbol; } } private final List<String> log = new ArrayList<>(); { log.add("instance block ran first"); } Main() { log.add("then the constructor"); } public static void main(String[] args) { String report = """ Operators: %s Log: %s """.formatted(Op.BY_SYMBOL.get('*') + " for '*'", new Main().log); System.out.print(report); System.out.println(report.lines().count() + " lines"); } }

Enum constants with bodies, and Interface.super

Each constant of Op brings its own apply: javac makes it an anonymous subclass of the enum (Main$Op$1), while getDeclaringClass() is still Op. The duck combines two interfaces' default methods with Swimmer.super and Flyer.super.

public class Main { interface Swimmer { default String move() { return "swims"; } } interface Flyer { default String move() { return "flies"; } } static class Duck implements Swimmer, Flyer { public String move() { return Swimmer.super.move() + " and " + Flyer.super.move(); } } enum Op { PLUS("+") { int apply(int a, int b) { return a + b; } }, TIMES("*") { int apply(int a, int b) { return a * b; } }; final String symbol; Op(String symbol) { this.symbol = symbol; } abstract int apply(int a, int b); } public static void main(String[] args) { System.out.println("A duck " + new Duck().move()); for (Op op : Op.values()) { System.out.println("6 " + op.symbol + " 7 = " + op.apply(6, 7) + " (" + op.getClass().getName() + ", declared in " + op.getDeclaringClass().getSimpleName() + ")"); } } }

Practice mode: test cases for a method

With a practice attribute, Run calls one method with test cases instead of running main, and reports each case. This solution has a bug; fix the comparison and run it again. The results also arrive in playpad-output as detail.cases, for your own grading.

class Solution { // The first index whose value is at least target. static int lowerBound(int[] sorted, int target) { int lo = 0, hi = sorted.length; while (lo < hi) { int mid = (lo + hi) / 2; if (sorted[mid] <= target) lo = mid + 1; else hi = mid; } return lo; } }

Errors read like the JDK's

Compile errors use javac's wording, carets and order; exceptions print Java stack traces, with the JDK's own NullPointerException messages. Both are checked against a real JDK 25, so what a learner sees here is what they will see from java Main.java.

void main() { int count = "three"; IO.println(total); } String find(String[] names, char initial) { for (int i = 0; i < names.length; i++) { if (names[i].charAt(0) == initial) return names[i]; } return null; } void main() { String[] names = {"Ada", "Grace", "Linus"}; IO.println(find(names, 'G').length()); IO.println(find(names, 'Z').length()); }

Stop, and one worker for the page

Every Java widget on a page shares one worker, so the engine starts once. Run the first widget, then the second: the second shows Queued… until it gets its turn. Its own Stop withdraws it without touching the first, and the first one's Stop ends the loop. A program left running is stopped after 200 million steps (a few seconds for this one; an empty loop gets there in well under a second), with a note explaining why.

void main() { IO.println("counting forever..."); StringBuilder line = new StringBuilder(); for (int i = 0; ; i++) { line.setLength(0); line.append(i).append(" squared is ").append((long) i * i); } } void main() { IO.println("my turn"); }

What is in the subset

Supported: compact source files and classic classes; several classes per file, nested and inner classes; constructors, this(...), new, fields and methods on any object, private, toString() with @Override; inheritance, interfaces, instanceof patterns, enums and records; try/catch/finally, throw, custom and checked exceptions; int, char, boolean, long, double, String and arrays of any dimension, including arrays of objects; static and instance methods with overloading and recursion; if, while, do, for and enhanced for, switch statements and expressions, labeled break/continue, return, ?:; Math and java.util.Random; String's everyday methods with Java's regex syntax, String.format and printf; System.out, System.err, System.exit, System.arraycopy, IO.print/println/readln and Scanner over the input box; import, generics at use sites, boxing, ArrayList, HashMap, HashSet, ArrayDeque, PriorityQueue, StringBuilder, Integer/Long/Double/Character/Boolean Arrays, List.of/Map.of, TreeMap, TreeSet, LinkedList and Stack; lambdas and method references for any functional interface, with Comparator's combinators, merge and computeIfAbsent; generic classes and methods, var, varargs, and the @Override/@SuppressWarnings annotations, with javac's warnings; anonymous and local classes; raw types; subList; streams and Collectors; float, byte and short; sealed classes and pattern matching in switch and instanceof; BigInteger, BigDecimal, EnumMap and EnumSet; BufferedReader, StringTokenizer, StreamTokenizer and PrintWriter; static imports; initializer blocks and text blocks; enum constants with bodies and Interface.super.

Not yet: parallel streams, var lambda parameters and files, among others. These are reported as not supported in this version, never run with a different meaning. The full table, method by method, is JAVA-COMPATIBILITY.md.

Java widgets fire the same events as every other language (playpad-run, playpad-output, playpad-error), so answer checking works the same way: compare stdout in playpad-output. Java is a trademark of Oracle and/or its affiliates; this engine is not affiliated with or certified by Oracle.