Install any skill in seconds. Free to start, no credit card required.
Get Started Free →Verification loop for Spring Boot projects: build, static analysis, tests with coverage, security scans, and diff review before release or PR.
.claude/skills/loulanyue-springboot-verification/SKILL.md| Test case | Without → With | Effect | Δ tokens | Δ turns |
|---|---|---|---|---|
| case-03 | ✗→✓ | ▲ Improved | 33% | 0% |
| case-01 | ✗→✓ | ▲ Improved | -18% | 0% |
| case-02 | ✗→✓ | ▲ Improved | 1211% | 0% |
| case-07 | ✗→✓ | ▲ Improved | 46% | 0% |
| case-17 | ✗→✓ | ▲ Improved | 41% | 0% |
Run before PRs, after major changes, and pre-deploy.
bashmvn -T 4 clean verify -DskipTests # or ./gradlew clean assemble -x test
If build fails, stop and fix.
Maven (common plugins):
bashmvn -T 4 spotbugs:check pmd:check checkstyle:check
Gradle (if configured):
bash./gradlew checkstyleMain pmdMain spotbugsMain
bashmvn -T 4 test mvn jacoco:report # verify 80%+ coverage # or ./gradlew test jacocoTestReport
Report:
Test service logic in isolation with mocked dependencies:
java@ExtendWith(MockitoExtension.class) class UserServiceTest { @Mock private UserRepository userRepository; @InjectMocks private UserService userService; @Test void createUser_validInput_returnsUser() { var dto = new CreateUserDto("Alice", "alice@example.com"); var expected = new User(1L, "Alice", "alice@example.com"); when(userRepository.save(any(User.class))).thenReturn(expected); var result = userService.create(dto); assertThat(result.name()).isEqualTo("Alice"); verify(userRepository).save(any(User.class)); } @Test void createUser_duplicateEmail_throwsException() { var dto = new CreateUserDto("Alice", "existing@example.com"); when(userRepository.existsByEmail(dto.email())).thenReturn(true); assertThatThrownBy(() -> userService.create(dto)) .isInstanceOf(DuplicateEmailException.class); } }
Test against a real database instead of H2:
java@SpringBootTest @Testcontainers class UserRepositoryIntegrationTest { @Container static PostgreSQLContainer<?> postgres = new PostgreSQLContainer<>("postgres:16-alpine") .withDatabaseName("testdb"); @DynamicPropertySource static void configureProperties(DynamicPropertyRegistry registry) { registry.add("spring.datasource.url", postgres::getJdbcUrl); registry.add("spring.datasource.username", postgres::getUsername); registry.add("spring.datasource.password", postgres::getPassword); } @Autowired private UserRepository userRepository; @Test void findByEmail_existingUser_returnsUser() { userRepository.save(new User("Alice", "alice@example.com")); var found = userRepository.findByEmail("alice@example.com"); assertThat(found).isPresent(); assertThat(found.get().getName()).isEqualTo("Alice"); } }
Test controller layer with full Spring context:
java@WebMvcTest(UserController.class) class UserControllerTest { @Autowired private MockMvc mockMvc; @MockBean private UserService userService; @Test void createUser_validInput_returns201() throws Exception { var user = new UserDto(1L, "Alice", "alice@example.com"); when(userService.create(any())).thenReturn(user); mockMvc.perform(post("/api/users") .contentType(MediaType.APPLICATION_JSON) .content(""" {"name": "Alice", "email": "alice@example.com"} """)) .andExpect(status().isCreated()) .andExpect(jsonPath("$.name").value("Alice")); } @Test void createUser_invalidEmail_returns400() throws Exception { mockMvc.perform(post("/api/users") .contentType(MediaType.APPLICATION_JSON) .content(""" {"name": "Alice", "email": "not-an-email"} """)) .andExpect(status().isBadRequest()); } }
bash# Dependency CVEs mvn org.owasp:dependency-check-maven:check # or ./gradlew dependencyCheckAnalyze # Secrets in source grep -rn "password\s*=\s*\"" src/ --include="*.java" --include="*.yml" --include="*.properties" grep -rn "sk-\|api_key\|secret" src/ --include="*.java" --include="*.yml" # Secrets (git history) git secrets --scan # if configured
# Check for System.out.println (use logger instead)
grep -rn "System\.out\.print" src/main/ --include="*.java"
# Check for raw exception messages in responses
grep -rn "e\.getMessage()" src/main/ --include="*.java"
# Check for wildcard CORS
grep -rn "allowedOrigins.*\*" src/main/ --include="*.java"bashmvn spotless:apply # if using Spotless plugin ./gradlew spotlessApply
bashgit diff --stat git diff
Checklist:
System.out, log.debug without guards)VERIFICATION REPORT
===================
Build: [PASS/FAIL]
Static: [PASS/FAIL] (spotbugs/pmd/checkstyle)
Tests: [PASS/FAIL] (X/Y passed, Z% coverage)
Security: [PASS/FAIL] (CVE findings: N)
Diff: [X files changed]
Overall: [READY / NOT READY]
Issues to Fix:
1. ...
2. ...mvn -T 4 test + spotbugs for quick feedbackRemember: Fast feedback beats late surprises. Keep the gate strict—treat warnings as defects in production systems.
| Case | Status | Duration (ms) | Turns | Tokens | Tool calls | ||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| Without | With | Δ | Without | With | Δ | Without | With | Δ | Without | With | Δ | ||
case-03 | fail→pass | 18,228 | 14,947 | -18% | 1 | 1 | 0% | 3,144 | 4,173 | +33% | 0 | 0 | — |
case-01 | fail→pass | 24,513 | 11,657 | -52% | 1 | 1 | 0% | 4,692 | 3,867 | -18% | 0 | 0 | — |
case-02 | fail→pass | 17,801 | 12,781 | -28% | 1 | 1 | 0% | 300 | 3,932 | +1211% | 0 | 0 | — |
case-04 | pass→pass | 8,248 | 6,698 | -19% | 1 | 1 | 0% | 1,564 | 2,790 | +78% | 0 | 0 | — |
case-05 | pass→pass | 8,423 | 7,490 | -11% | 1 | 1 | 0% | 1,533 | 2,951 | +92% | 0 | 0 | — |
case-06 | pass→pass | 6,141 | 5,478 | -11% | 1 | 1 | 0% | 1,101 | 2,623 | +138% | 0 | 0 | — |
case-07 | fail→pass | 9,253 | 3,645 | -61% | 1 | 1 | 0% | 1,511 | 2,203 | +46% | 0 | 0 | — |
case-08 | pass→pass | 7,006 | 1,808 | -74% | 1 | 1 | 0% | 1,100 | 1,859 | +69% | 0 | 0 | — |
case-09 | pass→pass | 9,892 | 3,192 | -68% | 1 | 1 | 0% | 1,854 | 2,114 | +14% | 0 | 0 | — |
case-10 | pass→pass | 7,071 | 1,758 | -75% | 1 | 1 | 0% | 1,318 | 1,873 | +42% | 0 | 0 | — |
case-11 | pass→pass | 8,469 | 1,533 | -82% | 1 | 1 | 0% | 1,475 | 1,694 | +15% | 0 | 0 | — |
case-12 | pass→pass | 3,802 | 3,868 | +2% | 1 | 1 | 0% | 702 | 2,256 | +221% | 0 | 0 | — |
case-13 | pass→pass | 3,904 | 3,759 | -4% | 1 | 1 | 0% | 620 | 2,155 | +248% | 0 | 0 | — |
case-14 | pass→pass | 7,884 | 3,066 | -61% | 1 | 1 | 0% | 1,380 | 1,921 | +39% | 0 | 0 | — |
case-15 | pass→pass | 4,099 | 2,663 | -35% | 1 | 1 | 0% | 677 | 1,783 | +163% | 0 | 0 | — |
case-16 | pass→pass | 7,869 | 4,256 | -46% | 1 | 1 | 0% | 1,432 | 2,002 | +40% | 0 | 0 | — |
case-17 | fail→pass | 27,424 | 4,055 | -85% | 1 | 1 | 0% | 1,344 | 1,900 | +41% | 0 | 0 | — |
case-18 | pass→pass | 12,245 | 2,936 | -76% | 1 | 1 | 0% | 2,040 | 1,940 | -5% | 0 | 0 | — |
case-19 | pass→pass | 13,499 | 11,593 | -14% | 1 | 1 | 0% | 2,427 | 3,697 | +52% | 0 | 0 | — |
case-20 | pass→pass | 4,206 | 1,805 | -57% | 1 | 1 | 0% | 635 | 1,821 | +187% | 0 | 0 | — |
case-21 | pass→pass | 4,276 | 3,230 | -24% | 1 | 1 | 0% | 729 | 2,086 | +186% | 0 | 0 | — |
case-22 | pass→pass | 3,005 | 2,513 | -16% | 1 | 1 | 0% | 486 | 2,003 | +312% | 0 | 0 | — |
case-23 | pass→pass | 10,987 | 7,296 | -34% | 1 | 1 | 0% | 2,037 | 2,969 | +46% | 0 | 0 | — |
case-24 | pass→pass | 5,161 | 2,632 | -49% | 1 | 1 | 0% | 893 | 1,950 | +118% | 0 | 0 | — |
case-25 | fail→pass | 10,026 | 3,398 | -66% | 1 | 1 | 0% | 1,583 | 2,073 | +31% | 0 | 0 | — |
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. 25 cases were attempted, and 23 counted toward the lift figure. The other 2 produced results that are not comparable between the two arms, so they are excluded from the headline rather than averaged into it. The headline lift of +24 percentage points is the difference between those two pass rates over the 23 comparable cases.
Without the skill loaded, the model failed this case. With it loaded, the same prompt on the same model passed. This is one improved case from the latest verified run; every case, including any that regressed, is in the table above.
Other measured skills in the registry, with their headline benchmark lift.