internal/skills/content/micronaut/SKILL.md
Micronaut framework guardrails, patterns, and best practices for AI-assisted development. Use when working with Micronaut projects, or when the user mentions Micronaut. Provides compile-time DI, HTTP server/client, data access, and cloud-native guidelines.
npx skillsauth add ar4mirez/samuel micronautInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
Applies to: Micronaut 4.x, Java 21+, Microservices, Serverless, CLI Applications, IoT
jakarta.inject annotations, not Spring-specific ones@Singleton, @Prototype, or @RequestScope@Factory for third-party objects that cannot be annotated@Requires for conditional bean loading@Autowired or Spring DI annotations@Controller("/path")@ExecuteOn(TaskExecutors.BLOCKING) for JDBC/blocking operations@Validated at class level for request validationHttpResponse<T> for status code control, direct type for 200 OK@Status(HttpStatus.CREATED) for POST endpoints@Operation, @ApiResponse) for all endpoints@JdbcRepository or @MongoRepository with dialect specificationPageableRepository<Entity, ID> for pagination support@Query for custom queries, prefer derived query methods when possible@Transactional at service layer, not repository layer@Transactional(readOnly = true) for read-only operationsschema-generate: CREATE)@Serdeable (Micronaut serialization)@NotBlank, @Email, @Size)componentModel = "jsr330" for entity-DTO mappingExceptionHandler<E, HttpResponse<T>> for each exception type@Requires(classes = {...}) on exception handlersapplication.yml with environment-specific overrides (application-dev.yml)${ENV_VAR:default}@ConfigurationProperties for type-safe configuration beans@MicronautTest for integration tests (starts embedded server)@MockBean for mocking dependencies in integration tests@ExtendWith(MockitoExtension.class) for unit testsTestPropertyProvider for dynamic test configurationshouldCreateUserWhenEmailIsUnique@ExecuteOn(TaskExecutors.BLOCKING) only for blocking operationsMono, Flux) for non-blocking I/O when appropriatebuild.gradle for productionmyapp/
├── src/main/
│ ├── java/com/example/
│ │ ├── Application.java # Entry point with @OpenAPIDefinition
│ │ ├── config/ # @Factory, @ConfigurationProperties
│ │ ├── controller/ # @Controller classes
│ │ ├── service/ # Business logic interfaces
│ │ │ └── impl/ # Service implementations
│ │ ├── repository/ # @JdbcRepository interfaces
│ │ ├── domain/ # @MappedEntity classes
│ │ ├── dto/ # @Serdeable records
│ │ ├── mapper/ # MapStruct @Mapper interfaces
│ │ └── exception/ # Custom exceptions + handlers
│ └── resources/
│ ├── application.yml # Main config (with -dev.yml, -prod.yml overrides)
│ ├── db/migration/ # Flyway SQL files
│ └── logback.xml
├── src/test/java/com/example/ # Unit + integration tests
├── build.gradle # Micronaut Gradle plugin
└── settings.gradle
service/impl/ separates interface from implementation for testabilitydto/ uses @Serdeable records for API request/response typesmapper/ bridges domain entities to DTOs via MapStructexception/ bundles custom exceptions with their ExceptionHandler implementations@Singleton
@RequiredArgsConstructor
public class UserServiceImpl implements UserService {
private final UserRepository userRepository;
private final UserMapper userMapper;
// Constructor injection -- resolved at compile time
}
@Factory
public class SecurityBeans {
@Singleton
public BCryptPasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder(12);
}
}
@Singleton
@Requires(env = "production")
public class ProductionEmailService implements EmailService { }
@Singleton
@Requires(env = "dev")
public class MockEmailService implements EmailService { }
@Controller("/api/users")
@Validated
@RequiredArgsConstructor
@ExecuteOn(TaskExecutors.BLOCKING)
@Secured(SecurityRule.IS_AUTHENTICATED)
@Tag(name = "Users", description = "User management endpoints")
public class UserController {
private final UserService userService;
@Post
@Status(HttpStatus.CREATED)
@Operation(summary = "Create a new user")
@ApiResponse(responseCode = "201", description = "User created")
@ApiResponse(responseCode = "409", description = "Email already exists")
public HttpResponse<UserResponse> createUser(@Body @Valid UserRequest request) {
UserResponse user = userService.createUser(request);
return HttpResponse.created(user)
.headers(h -> h.location(URI.create("/api/users/" + user.id())));
}
@Get("/{id}")
@Operation(summary = "Get user by ID")
public UserResponse getUserById(@PathVariable Long id) {
return userService.getUserById(id);
}
@Get
@Operation(summary = "List users with pagination")
public PageResponse<UserResponse> getAllUsers(
@QueryValue(defaultValue = "0") int page,
@QueryValue(defaultValue = "20") int size) {
return userService.getAllUsers(page, size);
}
@Put("/{id}")
public UserResponse updateUser(@PathVariable Long id, @Body @Valid UserRequest request) {
return userService.updateUser(id, request);
}
@Delete("/{id}")
@Status(HttpStatus.NO_CONTENT)
@Secured({"ROLE_ADMIN"})
public void deleteUser(@PathVariable Long id) {
userService.deleteUser(id);
}
}
@Serdeable
@MappedEntity("users")
@Data
@NoArgsConstructor
@AllArgsConstructor
@Builder
public class User {
@Id
@GeneratedValue(GeneratedValue.Type.AUTO)
private Long id;
@Column("email")
private String email;
@Column("password")
private String password;
@DateCreated
@Column("created_at")
private Instant createdAt;
@DateUpdated
@Column("updated_at")
private Instant updatedAt;
}
@JdbcRepository(dialect = Dialect.POSTGRES)
public interface UserRepository extends PageableRepository<User, Long> {
Optional<User> findByEmail(String email);
boolean existsByEmail(String email);
List<User> findByActiveTrue();
Page<User> findByRole(String role, Pageable pageable);
@Query("UPDATE User u SET u.active = :active WHERE u.id = :id")
void updateActiveStatus(Long id, boolean active);
}
@Serdeable
public record UserRequest(
@NotBlank @Email @Size(max = 255) String email,
@NotBlank @Size(min = 8, max = 100) String password,
@NotBlank @Size(max = 100) String firstName,
@NotBlank @Size(max = 100) String lastName
) {}
@Serdeable
public record UserResponse(
Long id, String email, String firstName, String lastName,
Boolean active, Instant createdAt, Instant updatedAt
) {}
@Serdeable
public record PageResponse<T>(
List<T> content, int page, int size,
long totalElements, int totalPages, boolean first, boolean last
) {
public static <T> PageResponse<T> of(List<T> content, int page, int size, long total) {
int totalPages = (int) Math.ceil((double) total / size);
return new PageResponse<>(content, page, size, total, totalPages,
page == 0, page >= totalPages - 1);
}
}
micronaut:
application:
name: myapp
server:
port: 8080
security:
authentication: bearer
token:
jwt:
signatures:
secret:
generator:
secret: ${JWT_SECRET}
jws-algorithm: HS256
intercept-url-map:
- pattern: /health/**
access: [isAnonymous()]
- pattern: /api/**
access: [isAuthenticated()]
datasources:
default:
url: jdbc:postgresql://localhost:5432/myapp
username: ${DB_USERNAME:postgres}
password: ${DB_PASSWORD:postgres}
flyway:
datasources:
default:
enabled: true
locations: classpath:db/migration
endpoints:
health: { enabled: true, sensitive: false }
prometheus: { enabled: true, sensitive: false }
public class ResourceNotFoundException extends RuntimeException {
private final String resourceName;
private final String fieldName;
private final Object fieldValue;
public ResourceNotFoundException(String resource, String field, Object value) {
super(String.format("%s not found with %s: '%s'", resource, field, value));
this.resourceName = resource;
this.fieldName = field;
this.fieldValue = value;
}
// getters
}
@Singleton
@Produces
@Requires(classes = {ResourceNotFoundException.class, ExceptionHandler.class})
public class ResourceNotFoundExceptionHandler
implements ExceptionHandler<ResourceNotFoundException, HttpResponse<ErrorResponse>> {
@Override
public HttpResponse<ErrorResponse> handle(HttpRequest request, ResourceNotFoundException ex) {
ErrorResponse error = ErrorResponse.of(404, "Not Found", ex.getMessage(), request.getPath());
return HttpResponse.notFound(error);
}
}
@ExtendWith(MockitoExtension.class)
@DisplayName("UserService")
class UserServiceTest {
@Mock private UserRepository userRepository;
@Mock private UserMapper userMapper;
private UserServiceImpl userService;
@BeforeEach
void setUp() {
userService = new UserServiceImpl(userRepository, userMapper);
}
@Test
@DisplayName("should create user when email is unique")
void shouldCreateUserWhenEmailIsUnique() {
when(userRepository.existsByEmail("[email protected]")).thenReturn(false);
when(userMapper.toEntity(any())).thenReturn(new User());
when(userRepository.save(any())).thenReturn(new User());
when(userMapper.toResponse(any())).thenReturn(expectedResponse);
UserResponse result = userService.createUser(request);
assertThat(result).isNotNull();
verify(userRepository).save(any());
}
}
@MicronautTest
@Testcontainers
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
class IntegrationTest implements TestPropertyProvider {
@Container
static PostgreSQLContainer<?> postgres = new PostgreSQLContainer<>("postgres:15-alpine");
@Inject @Client("/") HttpClient client;
@Override
public Map<String, String> getProperties() {
postgres.start();
return Map.of(
"datasources.default.url", postgres.getJdbcUrl(),
"datasources.default.username", postgres.getUsername(),
"datasources.default.password", postgres.getPassword()
);
}
@Test
void healthEndpoint_shouldReturnUp() {
var response = client.toBlocking().exchange(HttpRequest.GET("/health"), String.class);
assertThat(response.status().getCode()).isEqualTo(200);
}
}
./gradlew build # Build
MICRONAUT_ENVIRONMENTS=dev ./gradlew run # Run (dev profile)
./gradlew test # Run tests
./gradlew test jacocoTestReport # Tests + coverage report
./gradlew nativeCompile # GraalVM native image
./gradlew dockerBuild # Docker image
./gradlew dockerBuildNative # Native Docker image
./gradlew clean build # Clean build
./gradlew check # Lint (checkstyle/spotbugs)
@Serdeable for all DTOs@ExecuteOn(TaskExecutors.BLOCKING) for blocking operations@Transactional at the service layer@Autowired or Spring-specific annotationsFor detailed patterns and examples, see:
development
Zig language guardrails, patterns, and best practices for AI-assisted development. Use when working with Zig files (.zig), build.zig, or when the user mentions Zig. Provides comptime patterns, allocator conventions, C interop guidelines, and testing standards specific to this project's coding standards.
tools
WordPress framework guardrails, patterns, and best practices for AI-assisted development. Use when working with WordPress projects, or when the user mentions WordPress. Provides theme development, plugin architecture, REST API, blocks, and security guidelines.
tools
Toolkit for interacting with and testing local web applications using Playwright. Supports verifying frontend functionality, debugging UI behavior, capturing browser screenshots, and viewing browser logs. Use when testing web apps, automating browser interactions, or debugging frontend issues.
tools
Suite of tools for creating elaborate, multi-component web applications using modern frontend technologies (React, Tailwind CSS, shadcn/ui). Use for complex projects requiring state management, routing, or shadcn/ui components - not for simple single-file HTML/JSX pages.