원클릭으로
java-code-review
Use when reviewing Java code for null safety, exception handling, concurrency issues, and performance concerns.
Codex 또는 Claude로 설치 이 Prompt를 복사해 Codex, Claude 또는 다른 어시스턴트에 붙여 넣으면 Skill 페이지를 검토하고 설치를 진행할 수 있습니다.
메뉴
Use when reviewing Java code for null safety, exception handling, concurrency issues, and performance concerns.
Codex 또는 Claude로 설치 이 Prompt를 복사해 Codex, Claude 또는 다른 어시스턴트에 붙여 넣으면 Skill 페이지를 검토하고 설치를 진행할 수 있습니다.
SOC 직업 분류 기준
Use when implementing Spring Boot 3.x applications. Provides hands-on implementation patterns for web, data, security, testing, and cloud-native development.
Review REST API contracts for HTTP semantics, versioning, backward compatibility, and response consistency. Use when user asks "review API", "check endpoints", "REST review", or before releasing API changes.
Use when designing Spring Boot 3.x application architecture, making technology decisions, or planning project structure. Provides architectural patterns and references for project setup, JPA, security, testing, and reactive programming.
Use when designing, reviewing, or modifying REST API endpoints to enforce proper HTTP verbs, status codes, versioning, and API contract best practices.
Use when reviewing or writing code to enforce DRY, KISS, and YAGNI principles with Java-specific guidance.
Use when implementing or reviewing code that benefits from established design patterns like Builder, Factory, Strategy, Observer, Decorator, or Adapter.
| name | java-code-review |
| description | Use when reviewing Java code for null safety, exception handling, concurrency issues, and performance concerns. |
// Bad - returning null
public Customer findByEmail(String email) {
return customerRepository.findByEmail(email); // may return null
}
// Good - use Optional
public Optional<Customer> findByEmail(String email) {
return customerRepository.findByEmail(email);
}
// Good - handle Optional in callers
public CustomerResponse getCustomer(String email) {
return customerRepository.findByEmail(email)
.map(customerMapper::toResponse)
.orElseThrow(() -> new ResourceNotFoundException("Customer not found: " + email));
}
// Bad
public List<Order> findOrders(String customerId, String status, LocalDate from, LocalDate to) {
// callers pass null for unused filters
}
// Good - use a criteria object or overloaded methods
public record OrderSearchCriteria(
String customerId,
Optional<String> status,
Optional<LocalDate> from,
Optional<LocalDate> to
) {}
public List<Order> findOrders(OrderSearchCriteria criteria) {
Specification<Order> spec = Specification.where(null);
spec = spec.and(OrderSpecifications.hasCustomer(criteria.customerId()));
if (criteria.status().isPresent()) {
spec = spec.and(OrderSpecifications.hasStatus(criteria.status().get()));
}
if (criteria.from().isPresent()) {
spec = spec.and(OrderSpecifications.createdAfter(criteria.from().get()));
}
if (criteria.to().isPresent()) {
spec = spec.and(OrderSpecifications.createdBefore(criteria.to().get()));
}
return orderRepository.findAll(spec);
}
@Service
public class PaymentService {
private final PaymentGateway paymentGateway;
private final OrderRepository orderRepository;
private final AuditService auditService;
public PaymentService(PaymentGateway paymentGateway,
OrderRepository orderRepository,
AuditService auditService) {
this.paymentGateway = Objects.requireNonNull(paymentGateway, "paymentGateway must not be null");
this.orderRepository = Objects.requireNonNull(orderRepository, "orderRepository must not be null");
this.auditService = Objects.requireNonNull(auditService, "auditService must not be null");
}
}
public abstract class DomainException extends RuntimeException {
private final String errorCode;
protected DomainException(String errorCode, String message) {
super(message);
this.errorCode = errorCode;
}
public String getErrorCode() { return errorCode; }
}
public class ResourceNotFoundException extends DomainException {
public ResourceNotFoundException(String resource, Object id) {
super("NOT_FOUND", resource + " not found with id: " + id);
}
}
public class BusinessRuleViolationException extends DomainException {
public BusinessRuleViolationException(String message) {
super("BUSINESS_RULE_VIOLATION", message);
}
}
public class DuplicateResourceException extends DomainException {
public DuplicateResourceException(String resource, String field, Object value) {
super("DUPLICATE", resource + " already exists with " + field + ": " + value);
}
}
// Bad
try {
processPayment(order);
} catch (Exception e) {
log.error("Error", e);
// swallowed - what was the error?
}
// Good - catch specific exceptions
try {
processPayment(order);
} catch (PaymentDeclinedException e) {
log.warn("Payment declined for order {}: {}", order.getId(), e.getMessage());
order.setStatus(OrderStatus.PAYMENT_FAILED);
orderRepository.save(order);
} catch (PaymentGatewayException e) {
log.error("Payment gateway error for order {}", order.getId(), e);
throw new ServiceUnavailableException("Payment service is temporarily unavailable");
}
// Bad
InputStream is = new FileInputStream(file);
try {
// process
} finally {
is.close(); // may throw and mask original exception
}
// Good
try (InputStream is = new FileInputStream(file);
BufferedReader reader = new BufferedReader(new InputStreamReader(is, StandardCharsets.UTF_8))) {
return reader.lines().collect(Collectors.joining("\n"));
}
// Bad - not thread-safe
private final Map<String, Session> sessions = new HashMap<>();
// Good
private final Map<String, Session> sessions = new ConcurrentHashMap<>();
// Bad - race condition
private int counter = 0;
public void increment() { counter++; }
// Good
private final AtomicInteger counter = new AtomicInteger(0);
public void increment() { counter.incrementAndGet(); }
@Configuration
@EnableAsync
public class AsyncConfig implements AsyncConfigurer {
@Override
@Bean(name = "taskExecutor")
public Executor getAsyncExecutor() {
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
executor.setCorePoolSize(5);
executor.setMaxPoolSize(10);
executor.setQueueCapacity(25);
executor.setThreadNamePrefix("async-");
executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
executor.initialize();
return executor;
}
@Override
public AsyncUncaughtExceptionHandler getAsyncUncaughtExceptionHandler() {
return (throwable, method, params) ->
LoggerFactory.getLogger(method.getDeclaringClass())
.error("Async error in {}: {}", method.getName(), throwable.getMessage(), throwable);
}
}
@Service
public class ReportService {
private static final Logger log = LoggerFactory.getLogger(ReportService.class);
@Async("taskExecutor")
public CompletableFuture<ReportResult> generateReport(ReportRequest request) {
log.info("Generating report on thread: {}", Thread.currentThread().getName());
ReportResult result = buildReport(request);
return CompletableFuture.completedFuture(result);
}
}
@Service
public class AccountService {
private final AccountRepository accountRepository;
public AccountService(AccountRepository accountRepository) {
this.accountRepository = accountRepository;
}
@Transactional(isolation = Isolation.REPEATABLE_READ)
public void transfer(Long fromId, Long toId, BigDecimal amount) {
Account from = accountRepository.findByIdWithLock(fromId)
.orElseThrow(() -> new ResourceNotFoundException("Account", fromId));
Account to = accountRepository.findByIdWithLock(toId)
.orElseThrow(() -> new ResourceNotFoundException("Account", toId));
if (from.getBalance().compareTo(amount) < 0) {
throw new BusinessRuleViolationException("Insufficient funds");
}
from.setBalance(from.getBalance().subtract(amount));
to.setBalance(to.getBalance().add(amount));
accountRepository.save(from);
accountRepository.save(to);
}
}
public interface AccountRepository extends JpaRepository<Account, Long> {
@Lock(LockModeType.PESSIMISTIC_WRITE)
@Query("SELECT a FROM Account a WHERE a.id = :id")
Optional<Account> findByIdWithLock(@Param("id") Long id);
}
// Bad - N+1 problem
List<Order> orders = orderRepository.findAll();
for (Order order : orders) {
order.getItems().size(); // triggers lazy load for each order
}
// Good - fetch join
@Query("SELECT o FROM Order o JOIN FETCH o.items WHERE o.status = :status")
List<Order> findByStatusWithItems(@Param("status") OrderStatus status);
// Bad - loading everything
List<Product> allProducts = productRepository.findAll();
// Good - paginated
Page<Product> page = productRepository.findAll(PageRequest.of(0, 20, Sort.by("name")));
@Service
public class ConfigService {
private final ConfigRepository configRepository;
public ConfigService(ConfigRepository configRepository) {
this.configRepository = configRepository;
}
@Cacheable(value = "config", key = "#key")
public String getConfigValue(String key) {
return configRepository.findByKey(key)
.map(Config::getValue)
.orElseThrow(() -> new ResourceNotFoundException("Config", key));
}
@CacheEvict(value = "config", key = "#key")
@Transactional
public void updateConfig(String key, String value) {
Config config = configRepository.findByKey(key)
.orElseThrow(() -> new ResourceNotFoundException("Config", key));
config.setValue(value);
configRepository.save(config);
}
}
// Bad
String result = "";
for (String item : items) {
result += item + ", ";
}
// Good
String result = String.join(", ", items);
// Or for complex cases
StringBuilder sb = new StringBuilder();
for (int i = 0; i < items.size(); i++) {
if (i > 0) sb.append(", ");
sb.append(items.get(i).getName())
.append(": ")
.append(items.get(i).getValue());
}
String result = sb.toString();
List<String>, not List)