DZone
Thanks for visiting DZone today,
Edit Profile
  • Manage Email Subscriptions
  • How to Post to DZone
  • Article Submission Guidelines
Sign Out View Profile
  • Post an Article
  • Manage My Drafts
Over 2 million developers have joined DZone.
Log In / Join
Please enter at least three characters to search
Refcards Trend Reports
Events Video Library
Refcards
Trend Reports

Events

View Events Video Library

Zones

Culture and Methodologies Agile Career Development Methodologies Team Management
Data Engineering AI/ML Big Data Data Databases IoT
Software Design and Architecture Cloud Architecture Containers Integration Microservices Performance Security
Coding Frameworks Java JavaScript Languages Tools
Testing, Deployment, and Maintenance Deployment DevOps and CI/CD Maintenance Monitoring and Observability Testing, Tools, and Frameworks
Culture and Methodologies
Agile Career Development Methodologies Team Management
Data Engineering
AI/ML Big Data Data Databases IoT
Software Design and Architecture
Cloud Architecture Containers Integration Microservices Performance Security
Coding
Frameworks Java JavaScript Languages Tools
Testing, Deployment, and Maintenance
Deployment DevOps and CI/CD Maintenance Monitoring and Observability Testing, Tools, and Frameworks

Last call! Secure your stack and shape the future! Help dev teams across the globe navigate their software supply chain security challenges.

Modernize your data layer. Learn how to design cloud-native database architectures to meet the evolving demands of AI and GenAI workloads.

Releasing software shouldn't be stressful or risky. Learn how to leverage progressive delivery techniques to ensure safer deployments.

Avoid machine learning mistakes and boost model performance! Discover key ML patterns, anti-patterns, data strategies, and more.

Related

  • How Stalactite ORM Implements Its Fluent DSL
  • Spring Beans With Auto-Generated Implementations: How-To
  • What Java DAO Layer Is Best for Your Project
  • Dependency Injection in Spring

Trending

  • Blue Skies Ahead: An AI Case Study on LLM Use for a Graph Theory Related Application
  • Ethical AI in Agile
  • A Deep Dive Into Firmware Over the Air for IoT Devices
  • Performing and Managing Incremental Backups Using pg_basebackup in PostgreSQL 17
  1. DZone
  2. Coding
  3. Frameworks
  4. Graceful Shutdown: Spring Framework vs Golang Web Services

Graceful Shutdown: Spring Framework vs Golang Web Services

Discover how Spring and Golang handle graceful shutdowns, resource cleanup, and system signals. Learn the differences, pitfalls, and best practices.

By 
Ilia Ivankin user avatar
Ilia Ivankin
·
Mar. 13, 25 · Analysis
Likes (1)
Comment
Save
Tweet
Share
5.3K Views

Join the DZone community and get the full member experience.

Join For Free

Graceful application shutdown is a critical aspect of ensuring service reliability, particularly in high-load systems. 

In this article, we will explore how graceful shutdown is implemented in two popular technologies: Spring Framework and Golang, using examples that interact with a PostgreSQL database.

Why Did I Choose These Two Stacks for Comparison?

It's quite simple: when transitioning from Java and other JVM-based technologies to languages without a robust framework, developers are faced with the reality of managing many tasks manually. In Spring, many mechanisms for resource management come out of the box, while in Go, these processes need to be implemented by hand.

For example, many developers don't realize what Spring does under the hood. On the other hand, in Gohttps://dzone.com/articles/golang-tutorial-learn-golang-by-examples, we are responsible for ensuring proper resource cleanup to prevent issues like connection leaks in the database.

Let’s take a detailed look at how both technologies handle system signals, resource closure, connections, and background tasks, followed by a thorough comparison of their approaches.

The Concept of Graceful Shutdown

Before diving into code, let’s understand what a graceful shutdown is.

Graceful shutdown is a mechanism that allows an application to terminate cleanly after receiving a shutdown signal (e.g., SIGTERM or SIGINT).

The key objectives of a graceful shutdown are:

  1. Complete ongoing operations without accepting new ones.
  2. Release resources (e.g., database connections, files, communication channels).
  3. Shut down servers and background tasks.
  4. Prevent resource leaks and errors during restarts.

Graceful Shutdown Mechanism in Spring Framework

Spring Framework implements graceful shutdown through the application’s lifecycle (ApplicationContext) and automatic resource management.

When a Spring Boot application receives a shutdown signal (SIGTERM or SIGINT), it triggers a shutdown hook registered in the JVM:

Java
 
Runtime.getRuntime().addShutdownHook(new Thread(() -> {
    applicationContext.close();
}));


Steps Involved in Graceful Shutdown in Spring

1. Triggering the ContextClosedEvent

All event listeners can perform resource cleanup at this point.

Java
 
    @Component
    public class ShutdownListener implements ApplicationListener<ContextClosedEvent> {
        
      	@Override
        public void onApplicationEvent(ContextClosedEvent event) {
            System.out.println("Context is closing... Cleaning up resources.");
        }
    }


2. Calling @PreDestroy Methods

All beans annotated with @PreDestroy execute their shutdown logic.

Java
 
    @Component
    public class ExampleService {
      
        @PreDestroy
        public void onDestroy() {
            System.out.println("Bean is being destroyed...");
        }
    }


3. Closing Connection Pools

If the application uses a database with Spring Data and a connection pool (e.g., HikariCP), Spring automatically invokes the shutdown() method on the pool.

4. Terminating Background Tasks

Tasks created with @Scheduled or @Async are automatically stopped.

Basically, that's it. 

Silly photo of a drunk baby

Let's see an example.

Example of Graceful Shutdown in Spring

Java
 
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.CommandLineRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

import javax.annotation.PreDestroy;
import java.util.logging.Logger;

@SpringBootApplication
public class Application implements CommandLineRunner {

    private static final Logger logger = Logger.getLogger(Application.class.getName());

    @PreDestroy
    public void onShutdown() {
        logger.info("Application is shutting down gracefully...");
    }

    @Autowired
    private UserRepository userRepository;

    @Override
    public void run(String... args) {
        logger.info("Application started.");
        userRepository.findByName("Alice").ifPresent(user -> logger.info("User found: " + user.getName()));
    }

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


In this case, developers have minimal responsibilities regarding resource management, as Spring handles most of the shutdown logic. However, for custom cleanup, methods like @PreDestroy or implementing DisposableBean are essential.

Graceful Shutdown Mechanism in Golang

In Go, graceful shutdown requires explicit implementation. The key steps are:

  1. Handling system signals. 
    • The os/signal package is used to capture termination signals.
  2. Context and cancel functions.  
    • context.Context is used to control task termination.
  3. Manual resource cleanup.
    • All open resources and connections must be closed manually using methods like Close() or cancel().

Example of Graceful Shutdown in Go

Go
 
package main

import (
    "context"
    "fmt"
    "os"
    "os/signal"
    "syscall"
    "time"
  
    "github.com/jackc/pgx/v4"
)

// Background task with context
func backgroundTask(ctx context.Context) {
    for {
        select {
        case <-ctx.Done():
            fmt.Println("Background task stopped")
            return
        default:
            fmt.Println("Background task running...")
            time.Sleep(1 * time.Second)
        }
    }
}

func main() {
    fmt.Println("Starting application...")

    // Context for task management
    ctx, cancel := context.WithCancel(context.Background())

    // Database connection
    conn, err := pgx.Connect(ctx, "postgres://user:password@localhost:5432/mydb")
    if err != nil {
        fmt.Printf("Unable to connect to database: %v\n", err)
        return
    }
    defer conn.Close(ctx)

    // Start background task
    go backgroundTask(ctx)

    // Wait for termination signal
    stop := make(chan os.Signal, 1)
    signal.Notify(stop, syscall.SIGINT, syscall.SIGTERM)
    <-stop

    fmt.Println("Shutting down gracefully...")
    cancel() // Terminate tasks
    fmt.Println("Application stopped.")
}


That's not all — it's a good idea to create something like a Closer function that gathers all Close methods in the main function and then executes them.

In other words, all open connections and resources need to be closed manually by calling Close() methods or cancel() functions. To make this process more convenient and centralized, you can use the Closer function.

Let’s take a look at a universal Closer function for shutdown in Golang.

The Closer function allows you to pass a list of termination functions (cancel(), Close()) and automatically invoke them in the correct order. This helps prevent errors and simplifies the code.

Go
 
package main

import (
    "fmt"
    "sync"
)

func Closer(closeFuncs ...func()) {
    var wg sync.WaitGroup
  
    for _, closeFunc := range closeFuncs {
        wg.Add(1)
      
        go func(f func()) {
            defer wg.Done()
            f()
        }(closeFunc)
    }
  
    wg.Wait()
}


With this function, you can clean up multiple resources in parallel:

Go
 
func main() {
    ctx, cancel := context.WithCancel(context.Background())
    conn, _ := pgx.Connect(ctx, "postgres://user:password@localhost:5432/mydb")

    // Start background task
    go backgroundTask(ctx)

    // Wait for termination signal
    stop := make(chan os.Signal, 1)
    signal.Notify(stop, syscall.SIGINT, syscall.SIGTERM)
    <-stop

    Closer(
        func() { conn.Close(ctx) },
        cancel,
    )

    fmt.Println("Application stopped.")
}


Here:

1. Context and Background Tasks

The ctx context is used to manage tasks. When cancel() is called, background tasks automatically stop through the ctx.Done() channel.

2. Using the Closer Function

The Closer function takes termination functions (cancel(), Close(), etc.) and executes them in parallel. In this example, the database connection is closed using conn.Close().

  • The cancel() function is called to terminate all related tasks.

3. Completion Wait

  • The sync.WaitGroup ensures that all termination functions complete before the application shuts down.

Advantages of the Closer Function

  • Convenience. All resource cleanup functions can be grouped and passed in one place.
  • Parallel execution. Resource cleanup operations are performed in parallel, reducing overall shutdown time.
  • Safety. Using sync.WaitGroup prevents premature application termination by ensuring that all resources are fully released.

Important: The next section presents a simple comparison of the two implementations. If you're a professional, you can skip directly to the conclusion. This visualization is intended for colleagues who are either beginners or transitioning from other programming languages.

Comparison

feature spring framework golang
Signal handling Automatic via SpringApplication Manual via os/signal
Resource managemen @PreDestroy auto management  Manual with Close() and cancel()
Flexibility High-level abstraction Full control over processes and resources
Performance overhead Potentially higher due to abstraction layers Minimal overhead
Thread management Automatic (@Async, @Scheduled) or manual green threads Manual through goroutines


In instance, with database simple connections:

Feature Spring Framework (spring data) Golang (pgx)
Database connection Automatic via application.yml configuration Manual with pgx.Connect()
Transaction management Managed by @Transactional via AOP Transactions handled through context
Connection pool handling Automatic with pools like HikariCP Manual with explicit Close() calls (but there is pgxpool)
Error handling Exceptions with try-catch or Error Handler by Spring, or ControllerAdvice Errors returned from functions


Conclusion

What should you choose? There is no single correct answer — it depends on you!

  • Spring Framework offers high-level abstractions, making development faster and easier to maintain. However, this can reduce flexibility and introduce performance overhead.
  • Golang, on the other hand, requires more manual work but gives you full control over processes and resources. This approach is especially beneficial for microservices and high-load systems.

In any case, exploring different programming languages and frameworks is always valuable. It helps you understand how things work elsewhere and refine your own practices.

Take care!

Golang Spring Framework Framework Java (programming language)

Opinions expressed by DZone contributors are their own.

Related

  • How Stalactite ORM Implements Its Fluent DSL
  • Spring Beans With Auto-Generated Implementations: How-To
  • What Java DAO Layer Is Best for Your Project
  • Dependency Injection in Spring

Partner Resources

×

Comments
Oops! Something Went Wrong

The likes didn't load as expected. Please refresh the page and try again.

ABOUT US

  • About DZone
  • Support and feedback
  • Community research
  • Sitemap

ADVERTISE

  • Advertise with DZone

CONTRIBUTE ON DZONE

  • Article Submission Guidelines
  • Become a Contributor
  • Core Program
  • Visit the Writers' Zone

LEGAL

  • Terms of Service
  • Privacy Policy

CONTACT US

  • 3343 Perimeter Hill Drive
  • Suite 100
  • Nashville, TN 37211
  • support@dzone.com

Let's be friends:

Likes
There are no likes...yet! 👀
Be the first to like this post!
It looks like you're not logged in.
Sign in to see who liked this post!