Skip to main content
rule

springboot · sanjeed5 — Cursor rule: what it does, how to install

Enforces Spring Boot 3.x and Java 17+ conventions in Cursor. Guides code structure by feature, constructor injection, and clean API design.

Install into Cursor · Manually: put the files in place All methods and platforms ↓
.cursor/rules/springboot.mdc
Open on GitHub

What springboot does

This rule configures Cursor for modern Spring Boot 3.x development with Java 17+. Instead of grouping files into technical layers like global controller and repository folders, it organizes code around feature boundaries with dedicated api, domain, and infrastructure packages.

It prohibits field injection using @Autowired. All dependencies must use final fields and constructor injection via Lombok's @RequiredArgsConstructor. Controllers remain lean and stateless, passing requests and responses through dedicated DTOs while delegating work to service classes.

Logging with System.out.println or string concatenation is replaced by parameterized SLF4J calls. If your repository targets Java versions prior to 17 or runs Spring Boot 2.x, generated code will require adjustments.

What it helps with

  • structure Java packages by bounded context instead of technical layers
  • enforce constructor injection using final fields and Lombok
  • strip business logic out of controllers into dedicated service layers
  • handle application exceptions through a global @ControllerAdvice
  • standardize parameterized SLF4J logging across classes

Example prompts for the agent

  • Create a user registration endpoint using feature packaging and DTOs
  • Generate a global exception handler extending ResponseEntityExceptionHandler
  • Implement an order service using constructor injection with @RequiredArgsConstructor

How to install springboot

Manually: put the files in place

.cursor/rules/springboot.mdc

Frontmatter: description, globs, alwaysApply

Step by step, with commands for each platform: Cursor rules: .mdc files, four modes and what replaced .cursorrules

Questions about springboot

How to install springboot in Cursor?

Save the rule file at .cursor/rules/springboot.mdc in your workspace. Cursor automatically applies it whenever you work on files matching **/*.java.

What does the springboot rule do?

It instructs Cursor to follow Spring Boot 3.x architecture standards. The assistant organizes packages by feature context, uses constructor injection, moves logic away from controllers, and uses parameterized logging.

What Java versions are supported?

The guidelines are designed for Java 17+ and Spring Boot 3.x, relying on spring-boot-starter-parent or the official BOM for dependency management.

Source text

springboot.mdc · CC0-1.0
---
description: Enforces modern Spring Boot best practices for Java applications, covering code structure, dependency injection, API design, error handling, and testing to ensure maintainable, performant, and secure microservices.
globs: **/*.java
---
# springboot Best Practices

This guide outlines the definitive best practices for developing Spring Boot 3.x applications with Java 17+. Adhere to these rules to build robust, maintainable, and performant services.

## 1. Code Organization and Structure

Organize your codebase by feature or bounded context, not by technical layer. This improves navigability, cohesion, and testability.

❌ BAD: Technical Layering
```
com.myapp.project.controller.UserController
com.myapp.project.service.UserService
com.myapp.project.repository.UserRepository
```

✅ GOOD: Feature-based (Bounded Context)
```java
// Root application class in base package
package com.myapp.project;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class ProjectApplication {
    public static void main(String[] args) {
        SpringApplication.run(ProjectApplication.class, args);
    }
}

// User feature package
package com.myapp.project.user;

// User feature sub-packages
package com.myapp.project.user.api; // REST controllers, DTOs
package com.myapp.project.user.domain; // Business logic, entities
package com.myapp.project.user.infrastructure; // Repositories, external integrations
```

## 2. Dependency Management

Always inherit from `spring-boot-starter-parent` or use `spring-boot-dependencies` BOM for consistent dependency versions.

✅ GOOD: Maven `pom.xml`
```xml
<parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version>3.2.11</version> <!-- Use latest stable version -->
    <relativePath/>
</parent>
<dependencies>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    <!-- No version needed for Spring Boot managed dependencies -->
</dependencies>
```

## 3. Dependency Injection

Prefer constructor injection for all dependencies. This ensures immutability, simplifies testing, and makes dependencies explicit. Use Lombok's `@RequiredArgsConstructor` for conciseness.

❌ BAD: Field Injection
```java
@Service
public class UserService {
    @Autowired // Avoid field injection
    private UserRepository userRepository;
}
```

✅ GOOD: Constructor Injection with Lombok
```java
package com.myapp.project.user.domain;

import com.myapp.project.user.infrastructure.UserRepository;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;

@Service
@RequiredArgsConstructor // Automatically generates constructor for final fields
public class UserService {
    private final UserRepository userRepository; // Make dependencies final

    public User findUserById(Long id) {
        return userRepository.findById(id).orElseThrow(() -> new UserNotFoundException(id));
    }
}
```

## 4. API Design and Controllers

Controllers must be stateless and focused solely on routing HTTP requests and responses. Delegate all business logic to service layers. Use DTOs for request/response bodies.

❌ BAD: Business Logic in Controller
```java
@RestController
@RequestMapping("/api/users")
public class UserController {
    @PostMapping
    public ResponseEntity<User> createUser(@RequestBody User user) {
        // Business logic directly in controller - BAD
        if (user.getEmail() == null || !user.getEmail().contains("@")) {
            throw new IllegalArgumentException("Invalid email");
        }
        // ... more logic
        return ResponseEntity.ok(userRepository.save(user));
    }
}
```

✅ GOOD: Lean Controller, Delegate to Service
```java
package com.myapp.project.user.api;

import com.myapp.project.user.domain.UserService;
import com.myapp.project.user.api.dto.UserCreateRequest;
import com.myapp.project.user.api.dto.UserResponse;
import lombok.RequiredArgsConstructor;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;

@RestController
@RequestMapping("/api/users")
@RequiredArgsConstructor
public class UserController {
    private final UserService userService;

    @PostMapping
    public ResponseEntity<UserResponse> createUser(@RequestBody UserCreateRequest request) {
        UserResponse response = userService.createUser(request);
        return new ResponseEntity<>(response, HttpStatus.CREATED);
    }

    @GetMapping("/{id}")
    public ResponseEntity<UserResponse> getUserById(@PathVariable Long id) {
        UserResponse response = userService.findUserById(id);
        return ResponseEntity.ok(response);
    }
}
```

## 5. Logging

Use SLF4J with Logback (Spring Boot's default) for all logging. Avoid `System.out.print()`. Use Lombok's `@Slf4j` for convenience. Avoid string concatenation in log messages; use parameterized logging.

❌ BAD: `System.out.print()` and String Concatenation
```java
public void process(String data) {
    System.out.println("Processing data: " + data); // BAD
    log.info("Processing data: " + data); // Still BAD for performance
}
```

✅ GOOD: Parameterized SLF4J Logging
```java
package com.myapp.project.user.domain;

import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;

@Service
@Slf4j // Provides 'log' field
public class UserService {
    public void processData(String data) {
        log.info("Processing data: {}", data); // Use placeholders for performance
        try {
            // ... business logic
        } catch (Exception e) {
            log.error("Failed to process data: {}", data, e); // Include exception
        }
    }
}
```

## 6. Error Handling

Implement global exception handling using `@RestControllerAdvice` to provide consistent and meaningful error responses.

✅ GOOD: Global Exception Handler
```java
package com.myapp.project.common.exception;

import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.context.request.WebRequest;
import org.springframework.web.servlet.mvc.method.annotation.ResponseEntityExceptionHandler;

@ControllerAdvice
public class GlobalExceptionHandler extends ResponseEntityExceptionHandler {

    @ExceptionHandler(UserNotFoundException.class)
    public ResponseEntity<Object> handleUserNotFoundException(UserNotFoundException ex, WebRequest request) {
        ErrorResponse error = new ErrorResponse(HttpStatus.NOT_FOUND, ex.getMessage());
        return new ResponseEntity<>(error, HttpStatus.NOT_FOUND);
    }

    @ExceptionHandler(IllegalArgumentException.class)
    public ResponseEntity<Object> handleIllegalArgumentException(IllegalArgumentException ex, WebRequest request) {
        ErrorResponse error = new ErrorResponse(HttpStatus.BAD_REQUEST, ex.getMessage());
        return new ResponseEntity<>(error, HttpStatus.BAD_REQUEST);
    }

    // Define custom error response structure
    record ErrorResponse(HttpStatus status, String message) {}
}

// Custom exception example
package com.myapp.project.user.domain;

import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.ResponseStatus;

@ResponseStatus(HttpStatus.NOT_FOUND) // Can also be handled by @ControllerAdvice
public class UserNotFoundException extends RuntimeException {
    public UserNotFoundException(Long id) {
        super("Could not find user " + id);
    }
}
```

## 7. Configuration

Store all external configuration in `application.yml` or environment variables. Avoid hardcoding values. Use `@ConfigurationProperties` for type-safe configuration.

✅ GOOD: `application.yml`
```yaml
app:
  service:
    baseUrl: https://api.example.com
    timeoutMs: 5000
```

✅ GOOD: Type-Safe Configuration Class
```java
package com.myapp.project.common.config;

import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Configuration;

@Configuration
@ConfigurationProperties(prefix = "app.service")
@Data // Lombok for getters/setters
public class AppServiceProperties {
    private String baseUrl;
    private int timeoutMs;
}

// Usage
@Service
@RequiredArgsConstructor
public class ExternalServiceClient {
    private final AppServiceProperties properties;

    public void callExternalService() {
        // Use properties.getBaseUrl(), properties.getTimeoutMs()
    }
}
```

## 8. Testing

Write comprehensive unit and integration tests. Leverage Spring Boot's testing utilities. Constructor injection greatly aids unit testing.

✅ GOOD: Unit Test (Service Layer)
```java
package com.myapp.project.user.domain;

import com.myapp.project.user.infrastructure.UserRepository;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;

import java.util.Optional;

import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.mockito.Mockito.when;

class UserServiceTest {

    @Mock
    private UserRepository userRepository;

    @InjectMocks
    private UserService userService; // Injects mocks into UserService

    @BeforeEach
    void setUp() {
        MockitoAnnotations.openMocks(this);
    }

    @Test
    void findUserById_userExists_returnsUser() {
        User mockUser = new User(1L, "[email protected]");
        when(userRepository.findById(1L)).thenReturn(Optional.of(mockUser));

        User foundUser = userService.findUserById(1L);
        // Assertions...
    }

    @Test
    void findUserById_userNotFound_throwsException() {
        when(userRepository.findById(2L)).thenReturn(Optional.empty());

        assertThrows(UserNotFoundException.class, () -> userService.findUserById(2L));
    }
}
```

✅ GOOD: Integration Test (Controller Layer)
```java
package com.myapp.project.user.api;

import com.myapp.project.user.domain.UserService;
import com.myapp.project.user.api.dto.UserCreateRequest;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest;
import org.springframework.boot.test.mock.mockito.MockBean;
import org.springframework.http.MediaType;
import org.springframework.test.web.servlet.MockMvc;

import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.when;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;

@WebMvcTest(UserController.class) // Focuses on Web layer, mocks other beans
class UserControllerIntegrationTest {

    @Autowired
    private MockMvc mockMvc;

    @MockBean // Mocks the UserService bean
    private UserService userService;

    @Autowired
    private ObjectMapper objectMapper;

    @Test
    void createUser_validRequest_returnsCreated() throws Exception {
        UserCreateRequest request = new UserCreateRequest("[email protected]", "password");
        when(userService.createUser(any(UserCreateRequest.class)))
            .thenReturn(new UserResponse(1L, "[email protected]"));

        mockMvc.perform(post("/api/users")
                .contentType(MediaType.APPLICATION_JSON)
                .content(objectMapper.writeValueAsString(request)))
                .andExpect(status().isCreated());
    }
}
```

© sanjeed5 · CC0-1.0 · sanjeed5/awesome-cursor-rules-mdc

Similar IDE rules

All IDE rules for Backend and APIs →

springboot by other authors comes first.

From the same set240

Project sponsors

Companies that keep this analytics open
Gold sponsor

Impressions, clicks and code copies — open statistics. We keep the numbers public for the same reason NeBlask itself is public. Become a sponsor