Java + Spring Boot Cheatsheet

Complete development reference

Development
Contents
๐Ÿ—๏ธ

Project Setup

// Spring Initializr: https://start.spring.io
// Common dependencies:
//   - Spring Web
//   - Spring Data JPA
//   - Spring Security
//   - Spring Validation
//   - PostgreSQL / H2 Driver
//   - Lombok

// Project structure
src/main/java/com/example/demo/
โ”œโ”€โ”€ DemoApplication.java          // @SpringBootApplication
โ”œโ”€โ”€ controller/
โ”‚   โ””โ”€โ”€ UserController.java
โ”œโ”€โ”€ service/
โ”‚   โ””โ”€โ”€ UserService.java
โ”œโ”€โ”€ repository/
โ”‚   โ””โ”€โ”€ UserRepository.java
โ”œโ”€โ”€ model/
โ”‚   โ””โ”€โ”€ User.java
โ”œโ”€โ”€ dto/
โ”‚   โ”œโ”€โ”€ CreateUserRequest.java
โ”‚   โ””โ”€โ”€ UserResponse.java
โ”œโ”€โ”€ exception/
โ”‚   โ””โ”€โ”€ GlobalExceptionHandler.java
โ””โ”€โ”€ config/
    โ””โ”€โ”€ SecurityConfig.java

src/main/resources/
โ”œโ”€โ”€ application.yml
โ””โ”€โ”€ application-dev.yml

pom.xml Essentials

<!-- Spring Boot Parent -->
<parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version>3.3.0</version>
</parent>

<dependencies>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-data-jpa</artifactId>
    </dependency>
    <dependency>
        <groupId>org.projectlombok</groupId>
        <artifactId>lombok</artifactId>
        <optional>true</optional>
    </dependency>
</dependencies>
๐Ÿท๏ธ

Core Annotations

AnnotationPurpose
@SpringBootApplicationMain class โ€” combines @Configuration + @EnableAutoConfiguration + @ComponentScan
@RestControllerREST controller โ€” @Controller + @ResponseBody
@ServiceBusiness logic layer bean
@RepositoryData access layer bean (adds exception translation)
@ComponentGeneric Spring-managed bean
@ConfigurationJava-based configuration class
@BeanMethod-level โ€” registers return value as a bean
@AutowiredDependency injection (prefer constructor injection)
@Value("${key}")Inject property value
@Qualifier("name")Disambiguate beans of the same type
@PrimaryDefault bean when multiple candidates exist
@Profile("dev")Activate bean only in specific profile
@ConditionalOnPropertyBean creation based on configuration property
@TransactionalTransaction management on method/class
๐ŸŒ

REST Controllers

@RestController
@RequestMapping("/api/v1/users")
@RequiredArgsConstructor  // Lombok โ€” constructor injection
public class UserController {

    private final UserService userService;

    // GET /api/v1/users
    @GetMapping
    public List<UserResponse> getAll() {
        return userService.findAll();
    }

    // GET /api/v1/users/{id}
    @GetMapping("/{id}")
    public ResponseEntity<UserResponse> getById(@PathVariable Long id) {
        return ResponseEntity.ok(userService.findById(id));
    }

    // GET /api/v1/users?status=ACTIVE&page=0&size=10
    @GetMapping
    public Page<UserResponse> search(
        @RequestParam(defaultValue = "ACTIVE") String status,
        @RequestParam(defaultValue = "0") int page,
        @RequestParam(defaultValue = "10") int size
    ) {
        return userService.search(status, PageRequest.of(page, size));
    }

    // POST /api/v1/users
    @PostMapping
    @ResponseStatus(HttpStatus.CREATED)
    public UserResponse create(@Valid @RequestBody CreateUserRequest req) {
        return userService.create(req);
    }

    // PUT /api/v1/users/{id}
    @PutMapping("/{id}")
    public UserResponse update(@PathVariable Long id,
                               @Valid @RequestBody UpdateUserRequest req) {
        return userService.update(id, req);
    }

    // DELETE /api/v1/users/{id}
    @DeleteMapping("/{id}")
    @ResponseStatus(HttpStatus.NO_CONTENT)
    public void delete(@PathVariable Long id) {
        userService.delete(id);
    }
}
๐Ÿ—„๏ธ

JPA & Repositories

Entity

@Entity
@Table(name = "users")
@Data                  // Lombok: getters, setters, toString, equals, hashCode
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class User {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    @Column(nullable = false, length = 100)
    private String name;

    @Column(unique = true, nullable = false)
    private String email;

    @Enumerated(EnumType.STRING)
    private Status status = Status.ACTIVE;

    @OneToMany(mappedBy = "user", cascade = CascadeType.ALL, orphanRemoval = true)
    private List<Post> posts = new ArrayList<>();

    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "department_id")
    private Department department;

    @CreatedDate
    private LocalDateTime createdAt;

    @LastModifiedDate
    private LocalDateTime updatedAt;
}

Repository

@Repository
public interface UserRepository extends JpaRepository<User, Long> {

    // Derived query methods
    Optional<User> findByEmail(String email);
    List<User> findByStatus(Status status);
    List<User> findByNameContainingIgnoreCase(String name);
    boolean existsByEmail(String email);
    long countByStatus(Status status);

    // @Query (JPQL)
    @Query("SELECT u FROM User u WHERE u.status = :status ORDER BY u.createdAt DESC")
    Page<User> findActiveUsers(@Param("status") Status status, Pageable pageable);

    // Native query
    @Query(value = "SELECT * FROM users WHERE email LIKE %:domain", nativeQuery = true)
    List<User> findByEmailDomain(@Param("domain") String domain);

    // Modifying query
    @Modifying
    @Query("UPDATE User u SET u.status = :status WHERE u.id = :id")
    int updateStatus(@Param("id") Long id, @Param("status") Status status);
}

// JpaRepository provides:
// save(), saveAll(), findById(), findAll(), findAll(Pageable),
// deleteById(), delete(), count(), existsById()
โš™๏ธ

Services & DI

@Service
@RequiredArgsConstructor
@Transactional(readOnly = true)
public class UserService {

    private final UserRepository userRepo;
    private final UserMapper mapper;

    public List<UserResponse> findAll() {
        return userRepo.findAll()
            .stream()
            .map(mapper::toResponse)
            .toList();
    }

    public UserResponse findById(Long id) {
        return userRepo.findById(id)
            .map(mapper::toResponse)
            .orElseThrow(() -> new ResourceNotFoundException("User", id));
    }

    @Transactional
    public UserResponse create(CreateUserRequest req) {
        if (userRepo.existsByEmail(req.email())) {
            throw new DuplicateException("Email already exists");
        }
        User user = mapper.toEntity(req);
        return mapper.toResponse(userRepo.save(user));
    }

    @Transactional
    public void delete(Long id) {
        if (!userRepo.existsById(id)) {
            throw new ResourceNotFoundException("User", id);
        }
        userRepo.deleteById(id);
    }
}

// Constructor Injection (preferred โ€” no @Autowired needed with single constructor)
@Service
public class OrderService {
    private final OrderRepo orderRepo;
    private final PaymentService paymentService;

    public OrderService(OrderRepo orderRepo, PaymentService paymentService) {
        this.orderRepo = orderRepo;
        this.paymentService = paymentService;
    }
}
โœ…

Validation

// DTO with validation
public record CreateUserRequest(
    @NotBlank(message = "Name is required")
    @Size(min = 2, max = 100)
    String name,

    @NotBlank
    @Email(message = "Invalid email")
    String email,

    @NotNull
    @Min(18) @Max(150)
    Integer age,

    @Pattern(regexp = "^\\+?[1-9]\\d{1,14}$")
    String phone
) {}

// Common validation annotations:
// @NotNull, @NotBlank, @NotEmpty
// @Size(min, max), @Min, @Max
// @Email, @Pattern(regexp)
// @Past, @Future, @PastOrPresent
// @Positive, @PositiveOrZero, @Negative

// Use @Valid in controller to trigger validation
@PostMapping
public UserResponse create(@Valid @RequestBody CreateUserRequest req) {}
๐Ÿšจ

Exception Handling

// Custom Exception
@ResponseStatus(HttpStatus.NOT_FOUND)
public class ResourceNotFoundException extends RuntimeException {
    public ResourceNotFoundException(String resource, Object id) {
        super(resource + " not found with id: " + id);
    }
}

// Global Exception Handler
@RestControllerAdvice
@Slf4j
public class GlobalExceptionHandler {

    // Handle specific exception
    @ExceptionHandler(ResourceNotFoundException.class)
    @ResponseStatus(HttpStatus.NOT_FOUND)
    public ErrorResponse handleNotFound(ResourceNotFoundException ex) {
        return new ErrorResponse("NOT_FOUND", ex.getMessage());
    }

    // Handle validation errors
    @ExceptionHandler(MethodArgumentNotValidException.class)
    @ResponseStatus(HttpStatus.BAD_REQUEST)
    public Map<String, String> handleValidation(MethodArgumentNotValidException ex) {
        return ex.getBindingResult().getFieldErrors().stream()
            .collect(Collectors.toMap(
                FieldError::getField,
                fe -> fe.getDefaultMessage() != null ? fe.getDefaultMessage() : "Invalid"
            ));
    }

    // Catch-all
    @ExceptionHandler(Exception.class)
    @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
    public ErrorResponse handleAll(Exception ex) {
        log.error("Unexpected error", ex);
        return new ErrorResponse("INTERNAL_ERROR", "Something went wrong");
    }
}

public record ErrorResponse(String code, String message) {}
๐Ÿ”’

Spring Security

@Configuration
@EnableWebSecurity
@RequiredArgsConstructor
public class SecurityConfig {

    private final JwtAuthFilter jwtAuthFilter;

    @Bean
    public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
        return http
            .csrf(csrf -> csrf.disable())
            .sessionManagement(sm -> sm
                .sessionCreationPolicy(SessionCreationPolicy.STATELESS))
            .authorizeHttpRequests(auth -> auth
                .requestMatchers("/api/auth/**").permitAll()
                .requestMatchers("/api/admin/**").hasRole("ADMIN")
                .anyRequest().authenticated()
            )
            .addFilterBefore(jwtAuthFilter, UsernamePasswordAuthenticationFilter.class)
            .build();
    }

    @Bean
    public PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }

    @Bean
    public AuthenticationManager authManager(AuthenticationConfiguration config)
            throws Exception {
        return config.getAuthenticationManager();
    }
}

// Method-level security
@PreAuthorize("hasRole('ADMIN')")
public void deleteUser(Long id) { }

@PreAuthorize("#userId == authentication.principal.id")
public User getProfile(Long userId) { }
๐Ÿ“

Configuration

application.yml

spring:
  datasource:
    url: jdbc:postgresql://localhost:5432/mydb
    username: ${DB_USER:postgres}
    password: ${DB_PASS:secret}
    driver-class-name: org.postgresql.Driver
  jpa:
    hibernate:
      ddl-auto: validate    # none | validate | update | create-drop
    show-sql: true
    properties:
      hibernate:
        format_sql: true
  profiles:
    active: ${SPRING_PROFILE:dev}

server:
  port: 8080

logging:
  level:
    root: INFO
    com.example: DEBUG
    org.hibernate.SQL: DEBUG

# Custom properties
app:
  jwt:
    secret: ${JWT_SECRET}
    expiration: 86400000

Type-safe Config

@ConfigurationProperties(prefix = "app.jwt")
public record JwtProperties(
    String secret,
    long expiration
) {}

// Enable in main class:
@EnableConfigurationProperties(JwtProperties.class)
@SpringBootApplication
public class Application { }
โณ

Async & Scheduling

// Enable async
@Configuration
@EnableAsync
public class AsyncConfig { }

// Async method
@Async
public CompletableFuture<User> fetchUser(Long id) {
    User user = userRepo.findById(id).orElseThrow();
    return CompletableFuture.completedFuture(user);
}

// Parallel execution
CompletableFuture<User> userFuture = fetchUser(1L);
CompletableFuture<List<Order>> ordersFuture = fetchOrders(1L);
CompletableFuture.allOf(userFuture, ordersFuture).join();

// Scheduling
@Configuration
@EnableScheduling
public class SchedulerConfig { }

@Scheduled(fixedRate = 60000)  // every 60 seconds
public void cleanup() { }

@Scheduled(cron = "0 0 2 * * ?")  // daily at 2 AM
public void nightlyJob() { }
๐Ÿงช

Testing

// Unit Test (Service layer)
@ExtendWith(MockitoExtension.class)
class UserServiceTest {

    @Mock
    private UserRepository userRepo;
    @Mock
    private UserMapper mapper;
    @InjectMocks
    private UserService userService;

    @Test
    void findById_returnsUser() {
        User user = User.builder().id(1L).name("Bob").build();
        when(userRepo.findById(1L)).thenReturn(Optional.of(user));
        when(mapper.toResponse(user)).thenReturn(new UserResponse(1L, "Bob"));

        UserResponse result = userService.findById(1L);

        assertThat(result.name()).isEqualTo("Bob");
        verify(userRepo).findById(1L);
    }

    @Test
    void findById_throwsWhenNotFound() {
        when(userRepo.findById(99L)).thenReturn(Optional.empty());

        assertThrows(ResourceNotFoundException.class,
            () -> userService.findById(99L));
    }
}

// Integration Test (Controller layer)
@SpringBootTest
@AutoConfigureMockMvc
class UserControllerIT {

    @Autowired
    private MockMvc mockMvc;

    @Test
    void createUser_returns201() throws Exception {
        mockMvc.perform(post("/api/v1/users")
            .contentType(MediaType.APPLICATION_JSON)
            .content("""
                {"name": "Bob", "email": "bob@mail.com", "age": 25}
                """))
            .andExpect(status().isCreated())
            .andExpect(jsonPath("$.name").value("Bob"));
    }
}

// Test with @DataJpaTest (repository layer, in-memory DB)
@DataJpaTest
class UserRepositoryTest {
    @Autowired UserRepository repo;

    @Test
    void findByEmail_works() {
        repo.save(User.builder().name("Bob").email("bob@mail.com").build());
        Optional<User> found = repo.findByEmail("bob@mail.com");
        assertThat(found).isPresent();
    }
}
โœจ

Java Records & Modern Java

// Records (Java 16+) โ€” immutable data classes
public record UserResponse(Long id, String name, String email) {}

// Sealed classes (Java 17+)
public sealed interface Shape
    permits Circle, Rectangle, Triangle {}

public record Circle(double radius) implements Shape {}
public record Rectangle(double w, double h) implements Shape {}

// Pattern matching switch (Java 21+)
double area(Shape shape) {
    return switch (shape) {
        case Circle c    -> Math.PI * c.radius() * c.radius();
        case Rectangle r -> r.w() * r.h();
        case Triangle t  -> 0.5 * t.base() * t.height();
    };
}

// Text blocks (Java 15+)
String json = """
    {
        "name": "Bob",
        "age": 25
    }
    """;

// var (Java 10+)
var users = new ArrayList<User>();
var map = Map.of("key", "value");

// Stream API
List<String> names = users.stream()
    .filter(u -> u.getAge() > 18)
    .sorted(Comparator.comparing(User::getName))
    .map(User::getName)
    .distinct()
    .toList();

// Optional chaining
String email = findUser(id)
    .map(User::getEmail)
    .filter(e -> e.contains("@"))
    .orElse("N/A");
๐Ÿณ

Docker & Deployment

# Multi-stage Dockerfile
FROM eclipse-temurin:21-jdk-alpine AS build
WORKDIR /app
COPY . .
RUN ./mvnw package -DskipTests

FROM eclipse-temurin:21-jre-alpine
WORKDIR /app
COPY --from=build /app/target/*.jar app.jar
EXPOSE 8080
ENTRYPOINT ["java", "-jar", "app.jar"]
# docker-compose.yml
services:
  app:
    build: .
    ports:
      - "8080:8080"
    environment:
      - SPRING_PROFILE=prod
      - DB_USER=postgres
      - DB_PASS=secret
    depends_on:
      - db

  db:
    image: postgres:16-alpine
    environment:
      POSTGRES_DB: mydb
      POSTGRES_USER: postgres
      POSTGRES_PASSWORD: secret
    volumes:
      - pgdata:/var/lib/postgresql/data

volumes:
  pgdata:
๐ŸŽฏ

Common Patterns

DTO Mapper

@Component
public class UserMapper {

    public UserResponse toResponse(User user) {
        return new UserResponse(user.getId(), user.getName(), user.getEmail());
    }

    public User toEntity(CreateUserRequest req) {
        return User.builder()
            .name(req.name())
            .email(req.email())
            .build();
    }
}

// Or use MapStruct for auto-mapping:
@Mapper(componentModel = "spring")
public interface UserMapper {
    UserResponse toResponse(User user);
    User toEntity(CreateUserRequest req);
}

Specification Pattern (Dynamic Queries)

public class UserSpecs {
    public static Specification<User> hasStatus(Status status) {
        return (root, query, cb) -> cb.equal(root.get("status"), status);
    }

    public static Specification<User> nameLike(String name) {
        return (root, query, cb) ->
            cb.like(cb.lower(root.get("name")), "%" + name.toLowerCase() + "%");
    }
}

// Usage:
userRepo.findAll(hasStatus(ACTIVE).and(nameLike("bob")), pageable);
๐Ÿ’ก Best Practices
  • Use constructor injection (not field injection)
  • Keep controllers thin โ€” logic goes in services
  • Use DTOs โ€” never expose entities directly
  • Use @Transactional(readOnly = true) for read operations
  • Prefer records for DTOs and value objects
  • Use profiles for environment-specific config