Stop the slop.Ship to prod.

Curated Java LogoJava best practices, packaged as an easy-to-install skill, compatible with any coding agent.

$npx skills add https://github.com/virtuslab/agent-skills --skill java-best-practices

Works with your favorite agent

AMPAntigravityClaude CodeOpenClawClineCodexCursorDroidGeminiGitHub CopilotGooseKiloKiro CLIOpenCodeRooTraeWindsurf
AMPAntigravityClaude CodeOpenClawClineCodexCursorDroidGeminiGitHub CopilotGooseKiloKiro CLIOpenCodeRooTraeWindsurf
AMPAntigravityClaude CodeOpenClawClineCodexCursorDroidGeminiGitHub CopilotGooseKiloKiro CLIOpenCodeRooTraeWindsurf
AMPAntigravityClaude CodeOpenClawClineCodexCursorDroidGeminiGitHub CopilotGooseKiloKiro CLIOpenCodeRooTraeWindsurf

From outdated to outstanding

Stop settling for AI's first draft

Agent's output

WITHOUT SKILLS
public class TeamMatcher {
public List<List<String>> find(List<Engineer> engineers) {
List<List<String>> teams = new ArrayList<>();
List<String> currentTeam = new ArrayList<>();
for (int i = 0; i < engineers.size(); i++) {
Engineer e = engineers.get(i);
if (e.getLanguages().contains("Java")) {
currentTeam.add(e.getNickname());
if (currentTeam.size() == 5) {
teams.add(currentTeam);
currentTeam = new ArrayList<>();
}
}
}
if (!currentTeam.isEmpty()) teams.add(currentTeam);
return teams;
}
}
WITH SKILLS
enum Language { JAVA, KOTLIN, SCALA }
record Engineer(String nickname, Set<Language> languages) {
boolean knows(Language language) {
return languages.contains(language);
}
}
record Team(Set<Engineer> members) {}
private static final int EFFECTIVE_TEAM_SIZE = 5;
List<Team> potentiallyGoodTeams(List<Engineer> engineers) {
return engineers.stream()
.filter(e -> e.knows(Language.JAVA))
.gather(Gatherers.windowFixed(EFFECTIVE_TEAM_SIZE))
.map(Set::copyOf)
.map(Team::new)
.toList();
}

Showcase

Modern Java Best Practices

View more on GitHub

Records for data objects

Read more in SKILL.md
@Value
public class User {
String username;
String email;
}
public record User(String username, String email) {}

Sealed classes for domain modeling

Read more in SKILL.md
public interface PaymentResult {}
public class PaymentSuccess implements PaymentResult { ... }
public class PaymentFailure implements PaymentResult { ... }
// No compile-time exhaustiveness checking
public sealed interface PaymentResult
permits PaymentSuccess, PaymentFailure, PaymentPending {}
public record PaymentSuccess(String txId, Money amount)
implements PaymentResult {}
public record PaymentFailure(String reason, ErrorCode code)
implements PaymentResult {}
public record PaymentPending(String txId)
implements PaymentResult {}

Switch expressions with pattern matching

Read more in SKILL.md
String message;
switch (result) {
case PaymentSuccess s:
message = "Paid " + s.amount();
break;
case PaymentFailure f:
message = "Failed: " + f.reason();
break;
case PaymentPending p:
message = "Pending: " + p.transactionId();
break;
}
String message = switch (result) {
case PaymentSuccess success ->
"Paid %s".formatted(success.amount());
case PaymentFailure failure ->
"Failed: %s".formatted(failure.reason());
case PaymentPending pending ->
"Pending: %s".formatted(pending.transactionId());
};

Pattern matching for instanceof

Read more in SKILL.md
if (obj instanceof String) {
String s = (String) obj;
if (!s.isBlank()) {
return s.trim();
}
}
if (obj instanceof String s && !s.isBlank()) {
return s.trim();
}

Record patterns (destructuring)

Read more in SKILL.md
if (obj instanceof Point p) {
System.out.println(p.x() + ", " + p.y());
}
String desc = switch (shape) {
case Circle circle ->
"Circle r=" + circle.radius();
case Rect rect ->
"Rect " + rect.width() + "x" + rect.height();
};
if (obj instanceof Point(int x, int y)) {
System.out.println(x + ", " + y);
}
String desc = switch (shape) {
case Circle(double radius) ->
"Circle r=%s".formatted(radius);
case Rect(double width, double height) ->
"Rect %sx%s".formatted(width, height);
};

Unnamed variables

Read more in SKILL.md
catch (Exception ignored) { ... }
list.stream().map(x -> "constant").toList();
catch (Exception _) { ... }
list.stream().map(_ -> "constant").toList();

Text blocks for multi-line strings

Read more in SKILL.md
String query = "SELECT u.id, u.name " +
"FROM users u " +
"WHERE u.active = true " +
"ORDER BY u.name";
String query = """
SELECT u.id, u.name
FROM users u
WHERE u.active = true
ORDER BY u.name
""";

var when type is obvious

Read more in SKILL.md
List<User> users = userRepository.findAll();
ObjectMapper mapper = new ObjectMapper();
var users = userRepository.findAll();
var mapper = new ObjectMapper();

Null safety with Optional

Read more in SKILL.md
public User findByEmail(String email) {
return repository.get(email); // might return null
}
public Optional<User> findByEmail(String email) {
return Optional.ofNullable(repository.get(email));
}

Error handling

Read more in SKILL.md
public User getUser(UserId id) {
try {
return userRepository.findById(id).orElse(null);
} catch (Exception e) {
return null;
}
}
public User getUser(UserId id) {
return userRepository.findById(id)
.orElseThrow(() -> new UserNotFoundException(id));
}

Immutable collections

Read more in SKILL.md
var roles = Arrays.asList("ADMIN", "USER");
var config = new HashMap<>(Map.of("timeout", 30));
var tags = Collections.unmodifiableList(
Arrays.asList("java", "backend")
);
var roles = List.of("ADMIN", "USER");
var config = Map.of("timeout", 30, "retries", 3);
var tags = Set.of("java", "backend");

Sequenced collections

Read more in SKILL.md
var first = list.get(0);
var last = list.get(list.size() - 1);
var rev = new ArrayList<>(list);
Collections.reverse(rev);
var first = list.getFirst();
var last = list.getLast();
var rev = list.reversed();

String utilities

Read more in SKILL.md
str.trim().isEmpty()
str.trim()
String.format("Hello %s", name)
for (String line : text.split("\n")) { ... }
str.isBlank()
str.strip() // Unicode-aware
"Hello %s".formatted(name)
"-".repeat(80) // 80 dashes
text.lines() // Stream<String>

Streams for collection transformations

Read more in SKILL.md
var activeEmails = new ArrayList<String>();
for (User user : users) {
if (user.isActive()) {
activeEmails.add(user.getEmail());
}
}
var activeEmails = users.stream()
.filter(User::isActive)
.map(User::email)
.toList();

Virtual threads over reactive chains

Read more in SKILL.md
Mono.fromCallable(() -> fetchData(url))
.subscribeOn(Schedulers.boundedElastic())
.flatMap(data -> process(data))
.subscribe();
try (var executor = Executors.newVirtualThreadPerTaskExecutor()) {
var future = executor.submit(() -> fetchData(url));
return future.get();
}

Structured concurrency

Read more in SKILL.md
var executor = Executors.newFixedThreadPool(2);
var f1 = executor.submit(() -> fetchUser(id));
var f2 = executor.submit(() -> fetchOrder(id));
executor.shutdown();
return new Response(f1.get(), f2.get());
try (var scope = new StructuredTaskScope.ShutdownOnFailure()) {
var user = scope.fork(() -> fetchUser(id));
var order = scope.fork(() -> fetchOrder(id));
scope.join().throwIfFailed();
return new Response(user.get(), order.get());
}

Parameterized logging

Read more in SKILL.md
log.info("Processing order " + orderId + " for user " + userId);
log.info("Processing order {} for user {}", orderId, userId);

Composed method pattern

Read more in SKILL.md
public void processOrder(Order order) {
if (order.getItems().stream()
.mapToDouble(Item::getPrice).sum() > 100) {
order.setDiscount(0.1);
}
emailService.send(order.getCustomer().getEmail(),
"Order " + order.getId() + " confirmed");
repository.save(order);
}
public void processOrder(Order order) {
applyDiscountIfEligible(order);
notifyCustomer(order);
repository.save(order);
}

Know your stdlib

Read more in SKILL.md
Math.max(min, Math.min(max, value))
str.substring(5, 20).indexOf("@")
Set<Integer> emojis = Set.of(0x1F600, ...);
emojis.contains(codePoint)
Math.clamp(value, min, max)
str.indexOf("@", 5, 20) // no allocation
Character.isEmoji(codePoint)

Keep stream pipelines pure

Read more in SKILL.md
// Side effect in intermediate operation
users.stream()
.map(user -> {
emailService.send(user);
return user.email();
})
.toList();
// Unnecessary stream() when there is no pipeline
users.stream().forEach(user -> emailService.send(user));
// Iterable.forEach for simple side effects
users.forEach(user -> emailService.send(user));
// Pure filter, side effect only in terminal forEach
users.stream()
.filter(User::isActive)
.forEach(user -> emailService.send(user));
// Plain loop when you need break/continue
for (var user : users) {
emailService.send(user);
}

Modern file I/O

Read more in SKILL.md
var reader = new BufferedReader(new FileReader("config.json"));
var sb = new StringBuilder();
String line;
while ((line = reader.readLine()) != null) {
sb.append(line);
}
reader.close();
String content = Files.readString(Path.of("config.json"));
Files.writeString(Path.of("output.txt"), data);

Built-in HttpClient

Read more in SKILL.md
var conn = (HttpURLConnection) new URL(url).openConnection();
conn.setRequestMethod("GET");
var in = conn.getInputStream();
// ... manual stream reading
var client = HttpClient.newHttpClient();
var request = HttpRequest.newBuilder().uri(uri).GET().build();
var response = client.send(request, BodyHandlers.ofString());

java.time over legacy Date

Read more in SKILL.md
Date now = new Date();
Calendar cal = Calendar.getInstance();
cal.add(Calendar.DAY_OF_MONTH, 7);
Date nextWeek = cal.getTime();
var now = Instant.now();
var nextWeek = LocalDate.now().plusDays(7);
var meeting = LocalDate.of(2024, Month.MARCH, 15)
.atTime(10, 0)
.atZone(ZoneId.of("UTC"));

Use Spotless after each agent's change

Read more in SKILL.md
public List<String>getActiveUsers(){
return users.stream().filter(User::isActive)
.map(User::getName).collect(Collectors.toList());}
public List<String> getActiveUsers() {
return users.stream()
.filter(User::isActive)
.map(User::getName)
.toList();
}

and many more practices on GitHub

$npx skills add https://github.com/virtuslab/agent-skills --skill java-best-practices