Install any skill in seconds. Free to start, no credit card required.
Get Started Free →Use when writing Quarkus (CDI/Panache/RESTEasy) Java services: apply Quarkus idioms, not Spring defaults.
.claude/skills/quarkus-service-conventions/SKILL.md| Model | Eval pass | Runs |
|---|---|---|
| gemini-3.6-flash | 92% | 12 |
| Model | Lift | Δ tokens | Δ turns | Cases | Verified |
|---|---|---|---|---|---|
| gemini-3.6-flashbest | +45% | +94% | 0% | 22 | 54d ago |
| gemini-3.5-flash | +38% | — | 0% | 24 | 86d ago |
| Test case | Without → With | Effect | Δ tokens | Δ turns |
|---|---|---|---|---|
| case-12 | ✗→✓ | ▲ Improved | — | — |
| case-09 | ✗→✓ | ▲ Improved | — | — |
| case-14 | ✗→✓ | ▲ Improved | — | — |
| case-04 | ✗→✓ | ▲ Improved | — | — |
| case-07 | ✗→✓ | ▲ Improved | — | — |
Enforces Quarkus (CDI / Panache / RESTEasy Reactive / Mutiny) idioms when writing Java for a Quarkus service. Apply whenever the project is Quarkus — do NOT reach for Spring Boot annotations, naming, or layout. Every rule below is mandatory.
<Name>Resource (e.g. MarketResource, OrderResource).Never <Name>Controller.
resource/ package. Never controller/.resource/, service/, repository/, domain/ (entities),dto/, config/, mapper/.
@Path("/markets"). Method-level: @GET / @POST / @PUT / @DELETEplus a method-level @Path("/{id}") for sub-paths.
@PathParam("id"). Query params: @QueryParam("page").@Produces(MediaType.APPLICATION_JSON) /@Consumes(MediaType.APPLICATION_JSON).
@RestController, @GetMapping, @PostMapping, @RequestMapping,@PathVariable, or @RequestParam.
@ApplicationScoped. Never @Service, @Component,@RestController, or @Singleton (@Singleton is non-proxyable — interception breaks).
@Inject (constructor injection or field injection). Never@Autowired.
extends PanacheEntity (JPA) or extends PanacheMongoEntity (MongoDB,annotated @MongoEntity(collection = "...")).
public fields with NO getters and NO setters — Panache generates accessors atbuild time. Do not write private fields + boilerplate getters/setters.
Market.find("slug", slug),Market.findById(id), Market.listAll(). For an Optional, use .firstResultOptional(). Never inject a JpaRepository/CrudRepository.
Uni<T> (single) or Multi<T> (stream). NeverCompletableFuture, Reactor Mono/Flux, or a blocking T.
.chain(...), .onItem()..., .onFailure().... Handle absence with.onItem().ifNull().failWith(() -> ...).
Uni/Multi pipeline (it stalls the event loop).Uni from two subscribers, share it with .memoize().indefinitely()— do not call .subscribe() twice on the same cold Uni.
private static final Logger log = Logger.getLogger(Foo.class);(org.jboss.logging.Logger). Or CDI-inject it: @Inject Logger log;.
log.infof("fetch slug=%s", slug) / log.errorf(ex, "failed slug=%s", slug).LoggerFactory.getLogger(...) or {} placeholders.@ConfigMapping(prefix = "market") on an INTERFACE withmethod accessors (int maxPageSize();). For a single value, use @ConfigProperty(name = "market.max-page-size", defaultValue = "100").
@ConfigurationProperties or @Value.src/main/resources/application.properties. Never application.yml.src/main/resources/import.sql (Hibernate auto-loads it in dev/test).ExceptionMapper<E> annotated@Provider, or a @ServerExceptionMapper method (RESTEasy Reactive).
@RestControllerAdvice or @ExceptionHandler.@Valid on the parameter, combined with BeanValidation constraints (@NotNull, @NotBlank) on the DTO.
@QuarkusTest; replace a bean with @InjectMock; assert withRestAssured given().when().get("/markets/unknown").then().statusCode(404).
@SpringBootTest, @WebMvcTest, @MockBean, or MockMvc.@ExtendWith(MockitoExtension.class) with @Mock / @InjectMocks. No @QuarkusTest.
Resource naming + JAX-RS
java// BEFORE @RestController @RequestMapping("/markets") public class MarketController { @GetMapping("/{slug}") public Market find(@PathVariable String slug) { ... } } // AFTER @Path("/markets") @ApplicationScoped public class MarketResource { @GET @Path("/{slug}") @Produces(MediaType.APPLICATION_JSON) public Market find(@PathParam("slug") String slug) { ... } }
CDI bean + injection
java// BEFORE @Service public class MarketService { @Autowired private MarketRepository repo; } // AFTER @ApplicationScoped public class MarketService { private final MarketRepository repo; @Inject public MarketService(MarketRepository repo) { this.repo = repo; } }
Panache entity
java// BEFORE @Entity public class Market { @Id @GeneratedValue private Long id; private String name; public String getName() { return name; } public void setName(String n) { this.name = n; } } // AFTER @Entity public class Market extends PanacheEntity { public String name; public String status; // public fields, accessors generated at build time }
Panache query → Optional
java// BEFORE Optional<Market> m = marketRepository.findBySlug(slug); // AFTER Optional<Market> m = Market.find("slug", slug).firstResultOptional();
Reactive method
java// BEFORE public CompletableFuture<Order> placeOrder(OrderRequest req) { ... } // AFTER public Uni<Order> placeOrder(OrderRequest req) { return validate(req).chain(this::persist).chain(this::notifyFulfillment); }
Logging
java// BEFORE private static final Logger log = LoggerFactory.getLogger(MarketService.class); log.info("fetch slug={}", slug); // AFTER private static final Logger log = Logger.getLogger(MarketService.class); log.infof("fetch slug=%s", slug);
Configuration
java// BEFORE @ConfigurationProperties(prefix = "market") public class MarketProps { private int maxPageSize; /* getters */ } // AFTER @ConfigMapping(prefix = "market") public interface MarketConfig { int maxPageSize(); Duration cacheTtl(); }
Exception → 404
java// BEFORE @RestControllerAdvice class Handler { @ExceptionHandler(MarketNotFoundException.class) ResponseEntity<?> h(MarketNotFoundException e) { return ResponseEntity.status(404).build(); } } // AFTER @Provider public class MarketNotFoundMapper implements ExceptionMapper<MarketNotFoundException> { public Response toResponse(MarketNotFoundException e) { return Response.status(404).entity(ErrorResponse.from(e)).build(); } }
Integration test
java// BEFORE @WebMvcTest(MarketController.class) class T { @Autowired MockMvc mvc; @MockBean MarketService svc; } // AFTER @QuarkusTest class MarketResourceTest { @InjectMock MarketService svc; @Test void notFound() { given().when().get("/markets/unknown").then().statusCode(404); } }
@ApplicationScoped, not @Singleton. @Singleton exists but breaks proxying/interception.
extends PanacheMongoEntity + @MongoEntity(collection = "markets");same public-fields rule.
PanacheRepository<Market> bean isallowed, but do not mix active-record static finders and an injected repository in the same bounded context; pick one.
quarkus-config-yaml extension; default andidiomatic is application.properties.
memoize(); a hot/already-resolved Uni can be subscribedfreely.
@Inject field avoids proxyissues) — unlike Spring where constructor injection is strongly preferred. Either is fine here.
@RestController / @GetMapping; always @Path + @GET on a *Resource.@Service / @Autowired; always @ApplicationScoped + @Inject.@Singleton for a normal bean; always @ApplicationScoped.private entity fields + getters/setters; always public Panache fields.JpaRepository; always static Panache finders.CompletableFuture/Mono/Flux; always Uni/Multi.Uni/Multi; always compose with .chain/.onItem.{}; always JBoss Logger + infof/errorf + %s.@ConfigurationProperties/@Value/application.yml; always@ConfigMapping/@ConfigProperty + application.properties.
@RestControllerAdvice; always ExceptionMapper/@ServerExceptionMapper.@SpringBootTest/@MockBean/MockMvc; always @QuarkusTest/@InjectMock/RestAssured.*Controller and annotates @RestController because "Java REST service"reads as Spring.
@Service + field @Autowired for the service bean.CompletableFuture/Mono for an async method.LoggerFactory and {} placeholders.application.yml + @ConfigurationProperties.@RestControllerAdvice/@ExceptionHandler.@SpringBootTest/@WebMvcTest + @MockBean + MockMvc.*Resource in resource/, JAX-RS annotations only@ApplicationScoped + @Inject (never @Service/@Autowired/@Singleton)extends PanacheEntity, public fields, static findersUni/Multi, no blocking inside pipelineLogger + infof/errorf + %s@ConfigMapping/@ConfigProperty + application.propertiesExceptionMapper/@ServerExceptionMapper for error responses@QuarkusTest + @InjectMock + RestAssured; unit = Mockito only| Case | Status | Duration (ms) | Turns | Tokens | Tool calls | ||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| Without | With | Δ | Without | With | Δ | Without | With | Δ | Without | With | Δ | ||
case-18 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
case-12 | fail→pass | — | — | — | — | — | — | — | — | — | — | — | — |
case-09 | fail→pass | — | — | — | — | — | — | — | — | — | — | — | — |
case-14 | fail→pass | — | — | — | — | — | — | — | — | — | — | — | — |
case-19 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
case-04 | fail→pass | — | — | — | — | — | — | — | — | — | — | — | — |
case-13 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
case-21 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
case-22 | pass→pass | — | — | — | — | — | — | — | — | — | — | — | — |
case-07 | fail→pass | — | — | — | — | — | — | — | — | — | — | — | — |
case-08 | fail→pass | — | — | — | — | — | — | — | — | — | — | — | — |
case-01 | pass→pass | — | — | — | — | — | — | — | — | — | — | — | — |
case-20 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
case-05 | fail→pass | — | — | — | — | — | — | — | — | — | — | — | — |
case-10 | fail→pass | — | — | — | — | — | — | — | — | — | — | — | — |
case-15 | fail→pass | — | — | — | — | — | — | — | — | — | — | — | — |
case-17 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
case-02 | pass→pass | — | — | — | — | — | — | — | — | — | — | — | — |
case-16 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
case-11 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
case-03 | pass→pass | — | — | — | — | — | — | — | — | — | — | — | — |
case-06 | fail→pass | — | — | — | — | — | — | — | — | — | — | — | — |
DecimalAI ran this skill against gemini-3.6-flash twice over the same eval suite — once with the skill loaded and once without — and compared the two runs case by case. 22 cases were attempted. The headline lift of +45 percentage points is the difference between those two pass rates over the 22 comparable cases. 2 cases got worse with the skill loaded, and they are included in that figure.
The per-case answers from this run were removed by the retention sweep, so the case table below shows the verdicts without the text either arm produced. The counts above were recorded at the time and are unaffected. Answers are now kept for 180 days.
| Model | Method | Date | Lift |
|---|---|---|---|
| gemini-3.5-flash | verified | 6/27/2026 | +38% |
Other measured skills in the registry, with their headline benchmark lift.