SoFunction
Updated on 2025-04-13

Summary of 5 methods for implementing web message push in SpringBoot

In the project development,Real-time message pushIt has become a key technology to improve user experience. Whether it is a chat application, notification system, real-time data display, or collaborative office scenario, the server needs to be able to actively push messages to the client. This article will introduce in detail several mainstream solutions for implementing web message push in SpringBoot, helping developers choose the most suitable technology according to actual needs.

1. Why do you need to push messages?

The traditional HTTP request isThe client requests actively, and the server responds passivelymode. But in many scenarios, we need a server toActively push messages to the browser,For example:

  • Web version of instant messaging
  • Real-time update of financial data such as stocks and funds
  • System notifications and reminders
  • Real-time updates when collaborating on document editing
  • ......

2. Message push implementation plan

1. Short Polling

principle: The client frequently sends requests at fixed time intervals and asks the server whether there are new messages.

Implementation method

@RestController
@RequestMapping("/api/messages")
public class MessageController {
    
    private final Map<String, List<String>> userMessages = new ConcurrentHashMap<>();
    
    @GetMapping("/{userId}")
    public List<String> getMessages(@PathVariable String userId) {
        List<String> messages = (userId, new ArrayList<>());
        List<String> result = new ArrayList<>(messages);
        ();  // Clear read messages        return result;
    }
    
    @PostMapping("/{userId}")
    public void sendMessage(@PathVariable String userId, @RequestBody String message) {
        (userId, k -> new ArrayList<>()).add(message);
    }
}

Front-end implementation

function startPolling() {
    setInterval(() => {
        fetch('/api/messages/user123')
            .then(response => ())
            .then(messages => {
                if ( > 0) {
                    (msg => (msg));
                }
            });
    }, 3000); // Query every 3 seconds}

advantage

  • Simple implementation, no special server configuration is required
  • Good compatibility, supports almost all browsers and servers

shortcoming

  • High resource consumption, a large number of invalid requests
  • Poor real-time, affected by polling interval
  • High server load, especially when the number of users is large

2. Long Polling

principle: After the client sends the request, if there is no new message on the server,Keep the connection openUntil there is a new message or timeout, the client immediately initiates a new request.

Implementation method

@RestController
@RequestMapping("/api/long-polling")
public class LongPollingController {
    
    private final Map<String, DeferredResult<List<String>>> waitingRequests = new ConcurrentHashMap<>();
    private final Map<String, List<String>> pendingMessages = new ConcurrentHashMap<>();
    
    @GetMapping("/{userId}")
    public DeferredResult<List<String>> waitForMessages(@PathVariable String userId) {
        DeferredResult<List<String>> result = new DeferredResult<>(60000L, new ArrayList<>());
        
        // Check if there are any pending messages        List<String> messages = (userId);
        if (messages != null && !()) {
            List<String> messagesToSend = new ArrayList<>(messages);
            ();
            (messagesToSend);
        } else {
            // No news, waiting            (userId, result);
            
            (() -> (userId));
            (() -> (userId));
        }
        
        return result;
    }
    
    @PostMapping("/{userId}")
    public void sendMessage(@PathVariable String userId, @RequestBody String message) {
        // Check if there is a waiting request        DeferredResult<List<String>> deferredResult = (userId);
        
        if (deferredResult != null) {
            List<String> messages = new ArrayList<>();
            (message);
            (messages);
            (userId);
        } else {
            //Storage messages and wait for the next poll            (userId, k -> new ArrayList<>()).add(message);
        }
    }
}

Front-end implementation

function longPolling() {
    fetch('/api/long-polling/user123')
        .then(response => ())
        .then(messages => {
            if ( > 0) {
                (msg => (msg));
            }
            // Initiate the next long poll immediately            longPolling();
        })
        .catch(() => {
            // Delay it after an error and try again            setTimeout(longPolling, 5000);
        });
}

advantage

  • Reduce invalid requests, more efficient than short polling
  • Nearly real-time experience, push it immediately when there is any news
  • Good compatibility, almost all browsers support it

shortcoming

  • Server resource consumption, a large number of connections will occupy server resources
  • May be limited by timeout
  • It is difficult to deal with scenarios where servers actively push

3. Server-Sent Events (SSE)

principle: The server establishes a one-way connection with the client, and the server canContinuously push data to clients, without the need for repeated requests on the client.

SpringBoot implementation

@RestController
@RequestMapping("/api/sse")
public class SSEController {
    
    private final Map<String, SseEmitter> emitters = new ConcurrentHashMap<>();
    
    @GetMapping("/subscribe/{userId}")
    public SseEmitter subscribe(@PathVariable String userId) {
        SseEmitter emitter = new SseEmitter(Long.MAX_VALUE);
        
        (() -> (userId));
        (() -> (userId));
        (e -> (userId));
        
        // Send an initial event to keep the connection        try {
            (().name("INIT").data("Connection established"));
        } catch (IOException e) {
            (e);
        }
        
        (userId, emitter);
        return emitter;
    }
    
    @PostMapping("/publish/{userId}")
    public ResponseEntity<String> publish(@PathVariable String userId, @RequestBody String message) {
        SseEmitter emitter = (userId);
        if (emitter != null) {
            try {
                (()
                    .name("MESSAGE")
                    .data(message));
                return ("Message sent");
            } catch (IOException e) {
                (userId);
                return ().body("Send failed");
            }
        } else {
            return ().build();
        }
    }
    
    @PostMapping("/broadcast")
    public ResponseEntity<String> broadcast(@RequestBody String message) {
        List<String> deadEmitters = new ArrayList<>();
        
        ((userId, emitter) -> {
            try {
                (()
                    .name("BROADCAST")
                    .data(message));
            } catch (IOException e) {
                (userId);
            }
        });
        
        (emitters::remove);
        return ("Broadcast message sent");
    }
}

Front-end implementation

function connectSSE() {
    const eventSource = new EventSource('/api/sse/subscribe/user123');
    
    ('INIT', function(event) {
        ();
    });
    
    ('MESSAGE', function(event) {
        ('Received the message: ' + );
    });
    
    ('BROADCAST', function(event) {
        ('Received the broadcast: ' + );
    });
    
     = function() {
        ();
        // Reconnect logic can be implemented here        setTimeout(connectSSE, 5000);
    };
}

advantage

  • Real server push, save resources
  • Automatic reconnect mechanism
  • Support event type distinction
  • Lighter than WebSocket

shortcoming

  • One-way communication, the client cannot send data to the server through SSE
  • Connection limit, the browser has a limit on the number of SSE connections to the same domain name
  • IE browser does not support it

4. WebSocket

principle: WebSocket is a kind ofTwo-way communication protocol, provides full duplex communication channel on a single TCP connection.

SpringBoot Configuration

@Configuration
@EnableWebSocket
public class WebSocketConfig implements WebSocketConfigurer {
    
    @Override
    public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) {
        (new MessageWebSocketHandler(), "/ws/messages")
                .setAllowedOrigins("*");
    }
}

public class MessageWebSocketHandler extends TextWebSocketHandler {
    
    private static final Map<String, WebSocketSession> sessions = new ConcurrentHashMap<>();
    
    @Override
    public void afterConnectionEstablished(WebSocketSession session) throws Exception {
        String userId = extractUserId(session);
        (userId, session);
    }
    
    @Override
    protected void handleTextMessage(WebSocketSession session, TextMessage message) throws Exception {
        // Process messages received from the client        String payload = ();
        // Processing logic...    }
    
    @Override
    public void afterConnectionClosed(WebSocketSession session, CloseStatus status) throws Exception {
        String userId = extractUserId(session);
        (userId);
    }
    
    private String extractUserId(WebSocketSession session) {
        // Extract user ID from session        return ().getQuery().replace("userId=", "");
    }
    
    // Send a message to the specified user    public static void sendToUser(String userId, String message) {
        WebSocketSession session = (userId);
        if (session != null && ()) {
            try {
                (new TextMessage(message));
            } catch (IOException e) {
                (userId);
            }
        }
    }
    
    // Broadcast message    public static void broadcast(String message) {
        ((userId, session) -> {
            if (()) {
                try {
                    (new TextMessage(message));
                } catch (IOException e) {
                    (userId);
                }
            }
        });
    }
}

Front-end implementation

function connectWebSocket() {
    const socket = new WebSocket('ws://localhost:8080/ws/messages?userId=user123');
    
     = function() {
        ('WebSocket connection established');
        // Can send a message        (({type: 'JOIN', content: 'User connected'}));
    };
    
     = function(event) {
        const message = ();
        ('Received the message:', message);
    };
    
     = function() {
        ('WebSocket connection is closed');
        // Reconnect logic can be implemented here        setTimeout(connectWebSocket, 5000);
    };
    
     = function(error) {
        ('WebSocket Error:', error);
        ();
    };
}

advantage

  • Full duplex communication, the server and the client can send data to each other at any time
  • The best real-time, the lowest delay
  • High efficiency, no HTTP header is required after establishing a connection, and the data transmission volume is small
  • Supports binary data

shortcoming

  • Relatively complex implementation
  • High server requirements, need to handle a large number of concurrent connections
  • May be restricted by firewall

5. STOMP (based on WebSocket)

principle:STOMP (Simple Text Oriented Messaging Protocol) is a simple messaging protocol based on WebSocket, providingMore advanced messaging mode

SpringBoot Configuration

@Configuration
@EnableWebSocketMessageBroker
public class WebSocketConfig implements WebSocketMessageBrokerConfigurer {

    @Override
    public void configureMessageBroker(MessageBrokerRegistry registry) {
        // Enable a simple memory-based message broker        ("/topic", "/queue");
        // Set the prefix of the application        ("/app");
        // Set user destination prefix        ("/user");
    }

    @Override
    public void registerStompEndpoints(StompEndpointRegistry registry) {
        ("/ws")
                .setAllowedOrigins("*")
                .withSockJS(); // Add SockJS support    }
}

@Controller
public class MessageController {
    
    private final SimpMessagingTemplate messagingTemplate;
    
    public MessageController(SimpMessagingTemplate messagingTemplate) {
         = messagingTemplate;
    }
    
    // Process the messages sent by the client to /app/sendMessage    @MessageMapping("/sendMessage")
    public void processMessage(String message) {
        // Processing messages...    }
    
    // Process the messages sent by the client to /app/chat/{roomId} and broadcast to the corresponding chat room    @MessageMapping("/chat/{roomId}")
    @SendTo("/topic/chat/{roomId}")
    public ChatMessage chat(@DestinationVariable String roomId, ChatMessage message) {
        // Handle chat messages...        return message;
    }
    
    // Send private messages    @MessageMapping("/private-message")
    public void privateMessage(PrivateMessage message) {
        (
            (),  // The username of the recipient            "/queue/messages",      // destination            message                 // Message content        );
    }
    
    // REST API sends broadcast messages    @PostMapping("/api/broadcast")
    public ResponseEntity<String> broadcast(@RequestBody String message) {
        ("/topic/broadcast", message);
        return ("Message broadcast");
    }
    
    // REST API sends private messages    @PostMapping("/api/private-message/{userId}")
    public ResponseEntity<String> sendPrivateMessage(
            @PathVariable String userId,
            @RequestBody String message) {
        (userId, "/queue/messages", message);
        return ("Private message sent");
    }
}

Front-end implementation

const stompClient = new ({
    brokerURL: 'ws://localhost:8080/ws',
    connectHeaders: {
        login: 'user',
        passcode: 'password'
    },
    debug: function (str) {
        (str);
    },
    reconnectDelay: 5000,
    heartbeatIncoming: 4000,
    heartbeatOutgoing: 4000
});

 = function (frame) {
    ('Connected: ' + frame);
    
    // Subscribe to broadcast messages    ('/topic/broadcast', function (message) {
        ('Received the broadcast: ' + );
    });
    
    // Subscribe to a specific chat room    ('/topic/chat/room1', function (message) {
        const chatMessage = ();
        ('Chat message: ' + );
    });
    
    // Subscribe to private messages    ('/user/queue/messages', function (message) {
        ('Received a private message: ' + );
    });
    
    // Send a message to the chat room    ({
        destination: '/app/chat/room1',
        body: ({
            sender: 'user123',
            content: 'Hello everyone!  ',
            timestamp: new Date()
        })
    });
    
    // Send private messages    ({
        destination: '/app/private-message',
        body: ({
            sender: 'user123',
            recipient: 'user456',
            content: 'Hello, this is a private message',
            timestamp: new Date()
        })
    });
};

 = function (frame) {
    ('STOMP error: ' + ['message']);
    ('Additional details: ' + );
};

();

advantage

  • Advanced Message Mode: Subscriptions, peer-to-peer messaging
  • Built-in message broker, simplify message routing
  • Support message acknowledgement and transactions
  • Framework support improvementSpringBoot has high integration
  • Support certification and authorization

shortcoming

  • Steep learning curve
  • High resource consumption
  • Relatively complex configuration

3. Solution comparison and selection suggestions

plan Real-time Two-way communication Resource consumption Implement complexity Browser compatibility
Short polling Low no high Low Excellent
Long polling middle no middle middle good
SSE high No (one-way) Low Low IE does not support it
WebSocket Extremely high yes Low high Good (compatibility needs to be considered)
STOMP Extremely high yes middle high Good (compatibility needs to be considered)

Select a suggestion

  • Simple notification scenario: There is no high requirement for real-time, you can chooseShort pollingorLong polling
  • One-way push data on the server: For example, real-time data display, notifications and reminders, etc., it is recommended to useSSE
  • Real-time requirements and requires two-way communication: For example, chat applications, online games, etc., you should chooseWebSocket
  • Complex messaging requirements: If you need topic subscription, point-to-point messages, message confirmation and other functions, it is recommended to useSTOMP
  • Old browsers need to be considered: SSE and WebSocket should be avoided, or downgrade solutions should be provided

4. Summary

Implement web message push in SpringBoot, there are many technical solutions to choose from, each solution has its applicable scenarios:

  • Short polling: The simplest but least efficient scenario, suitable for non-real-time requirements
  • Long polling: Improved version of polling reduces server load and improves real-time
  • SSE: Lightweight server push technology, suitable for one-way communication scenarios
  • WebSocket: The most powerful two-way communication solution, suitable for high real-time requirements scenarios
  • STOMP: WebSocket-based messaging protocol provides more advanced messaging functions

Choose the right push technologyAccording to business needsPerformance requirementsandBrowser compatibilityConsideration of factors such as the combination. In practical applications, a variety of technologies can also be combined to provide elegant downgrade solutions to ensure a good user experience in various environments.

The above is the detailed content of the 5 methods for SpringBoot to implement web message push. For more information about SpringBoot web message push, please pay attention to my other related articles!