This post explains some of the most useful Spring annotations with minimal code you can paste into a project. We’ll cover what each annotation does, when to use it, and a short example.
@EnableScheduling and @Scheduled #
- What they do
- @EnableScheduling: Turns on Spring’s scheduled task processing.
- @Scheduled: Marks a method to run on a schedule.
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.annotation.EnableScheduling;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;
@Configuration
@EnableScheduling
class SchedulingConfig { }
@Component
class ReportScheduler {
// Runs every 5 seconds after previous start
@Scheduled(fixedRate = 5_000)
public void exportSummary() {
System.out.println("exportSummary @ " + java.time.Instant.now());
}
// Cron: 09:00 Monday–Friday in Europe/Istanbul
@Scheduled(cron = "0 0 9 * * MON-FRI", zone = "Europe/Istanbul")
public void morningDigest() {
System.out.println("morningDigest @ " + java.time.Instant.now());
}
}
@EnableAsync and @Async #
- What they do
- @EnableAsync: Enables Spring’s async method execution infrastructure.
- @Async: Runs a method on a separate thread (from a configured executor).
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.annotation.EnableAsync;
import org.springframework.scheduling.annotation.Async;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import org.springframework.stereotype.Service;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.Executor;
@Configuration
@EnableAsync
class AsyncConfig {
@Bean(name = "taskExecutor")
Executor taskExecutor() {
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
executor.setCorePoolSize(4);
executor.setMaxPoolSize(8);
executor.setQueueCapacity(100);
executor.setThreadNamePrefix("async-");
executor.initialize();
return executor;
}
}
@Service
class EmailService {
@Async("taskExecutor")
public CompletableFuture<Void> sendWelcomeEmail(String to) {
try {
// simulate I/O
Thread.sleep(500);
System.out.println("sent welcome email to: " + to);
return CompletableFuture.completedFuture(null);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
return CompletableFuture.failedFuture(e);
}
}
}
@Component and @Service #
- What they do
- @Component: Generic stereotype to mark a class for component scanning.
- @Service: Specialization of
@Component, semantically indicating business logic.
import org.springframework.stereotype.Component;
import org.springframework.stereotype.Service;
@Component
class SlugGenerator {
String toSlug(String title) {
return title.toLowerCase().replaceAll("[^a-z0-9]+", "-").replaceAll("(^-|-$)", "");
}
}
@Service
class BlogService {
private final SlugGenerator slugGenerator;
BlogService(SlugGenerator slugGenerator) {
this.slugGenerator = slugGenerator;
}
String createSlug(String title) {
return slugGenerator.toSlug(title);
}
}
@Configuration and @Bean #
- What they do
- @Configuration: Declares a class that defines beans via
@Beanmethods. - @Bean: Declares a Spring bean returned by the annotated method.
- @Configuration: Declares a class that defines beans via
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.stereotype.Service;
import java.time.Clock;
@Configuration
class AppConfig {
@Bean
Clock clock() {
return java.time.Clock.systemUTC();
}
}
@Service
class TimeService {
private final Clock clock;
TimeService(Clock clock) {
this.clock = clock;
}
java.time.Instant now() {
return java.time.Instant.now(clock);
}
}
Tips and gotchas #
- @Async self-invocation: Calls within the same class won’t go through the proxy, so
@Asyncwon’t take effect. Call from another Spring bean. - @Async return types: Prefer
CompletableFuture<T>for error handling. Forvoid, use anAsyncUncaughtExceptionHandlerif you need to observe errors. - Scheduling threads: Default scheduler is limited. For parallel schedules, define a
TaskSchedulerbean or useThreadPoolTaskScheduler. - Cron time zone: Always set the
zoneattribute if your business time zone differs from server time. - @Configuration vs @Component: Use
@Configurationfor full configuration (ensures@Beanmethods return managed singletons even when called within the class).