How to Send iMessages from Java / Spring Boot
Java developers can integrate iMessage into Spring Boot applications using the Sendblue REST API. This tutorial covers sending messages with both RestTemplate and WebClient, setting up webhook controllers, handling media attachments, and implementing async message sending with retry logic.
Prerequisites
You need:
- Java 17+ and Spring Boot 3.x
- Sendblue API credentials — sign up free to get your API key and secret
- Spring Web dependency for REST controllers and HTTP clients
- Optionally, Spring WebFlux for reactive WebClient
Add the following to your application.properties:
sendblue.api.key=${SENDBLUE_API_KEY}
sendblue.api.secret=${SENDBLUE_API_SECRET}
sendblue.api.base-url=https://api.sendblue.coUsing RestTemplate
RestTemplate is the traditional way to make HTTP calls in Spring Boot. Here's a complete service class:
// src/main/java/com/example/service/SendblueService.java
package com.example.service;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.*;
import org.springframework.stereotype.Service;
import org.springframework.web.client.RestTemplate;
import java.util.*;
@Service
public class SendblueService {
@Value("${sendblue.api.key}")
private String apiKey;
@Value("${sendblue.api.secret}")
private String apiSecret;
@Value("${sendblue.api.base-url}")
private String baseUrl;
private final RestTemplate restTemplate = new RestTemplate();
public Map<String, Object> sendMessage(
String number, String content,
String sendStyle, String mediaUrl) {
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_JSON);
headers.set("sb-api-key-id", apiKey);
headers.set("sb-api-secret-key", apiSecret);
Map<String, String> body = new HashMap<>();
body.put("number", number);
body.put("content", content);
if (sendStyle != null) body.put("send_style", sendStyle);
if (mediaUrl != null) body.put("media_url", mediaUrl);
HttpEntity<Map<String, String>> request =
new HttpEntity<>(body, headers);
ResponseEntity<Map> response = restTemplate.postForEntity(
baseUrl + "/api/send-message", request, Map.class);
return response.getBody();
}
}Use the service in a controller or anywhere Spring can inject it:
@Autowired
private SendblueService sendblue;
// Send a message
sendblue.sendMessage("+15551234567", "Hello from Java!", "celebration", null);Using WebClient (Reactive)
For non-blocking I/O in reactive Spring Boot applications, use WebClient:
// src/main/java/com/example/service/SendblueReactiveService.java
package com.example.service;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import org.springframework.web.reactive.function.client.WebClient;
import reactor.core.publisher.Mono;
import java.util.*;
@Service
public class SendblueReactiveService {
private final WebClient webClient;
public SendblueReactiveService(
@Value("${sendblue.api.key}") String apiKey,
@Value("${sendblue.api.secret}") String apiSecret,
@Value("${sendblue.api.base-url}") String baseUrl) {
this.webClient = WebClient.builder()
.baseUrl(baseUrl)
.defaultHeader("sb-api-key-id", apiKey)
.defaultHeader("sb-api-secret-key", apiSecret)
.build();
}
public Mono<Map> sendMessage(String number, String content) {
Map<String, String> body = Map.of(
"number", number,
"content", content
);
return webClient.post()
.uri("/api/send-message")
.bodyValue(body)
.retrieve()
.bodyToMono(Map.class);
}
public Mono<Map> evaluateService(String number) {
return webClient.get()
.uri(uriBuilder -> uriBuilder
.path("/api/evaluate-service")
.queryParam("number", number)
.build())
.retrieve()
.bodyToMono(Map.class);
}
}WebClient is ideal for high-throughput applications where you need to send many messages concurrently without blocking threads.
Spring Boot Webhook Controller
Create a REST controller to handle incoming messages from Sendblue webhooks:
// src/main/java/com/example/controller/WebhookController.java
package com.example.controller;
import com.example.service.SendblueService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import java.util.Map;
@RestController
@RequestMapping("/webhooks")
public class WebhookController {
private static final Logger log =
LoggerFactory.getLogger(WebhookController.class);
@Autowired
private SendblueService sendblue;
@PostMapping("/receive")
public ResponseEntity<Map<String, String>> receiveMessage(
@RequestBody Map<String, Object> payload) {
String fromNumber = (String) payload.get("from_number");
String content = (String) payload.get("content");
String mediaUrl = (String) payload.get("media_url");
String service = (String) payload.get("service");
log.info("[{}] {} : {}", service, fromNumber, content);
// Process message (save to DB, trigger auto-reply, etc.)
sendblue.sendMessage(
fromNumber,
"Thanks for your message! We'll respond shortly.",
null, null
);
return ResponseEntity.ok(Map.of("status", "ok"));
}
}Configure your webhook URL in the Sendblue dashboard. For local development, use ngrok: ngrok http 8080.
Send Media Attachments
Attach images, videos, PDFs, or contact cards to any message:
// Send an image
sendblue.sendMessage(
"+15551234567",
"Here's your report:",
null,
"https://example.com/report.pdf"
);
// Send a contact card (vCard) — unique to Sendblue
sendblue.sendMessage(
"+15551234567",
null,
null,
"https://example.com/sales-contact.vcf"
);Sendblue supports all file types. Contact card (.vcf) delivery is a unique feature — when recipients save the card, your business name appears on all future messages. This is powerful for sales outreach.
Async Message Sending with Retry
For production applications, send messages asynchronously with automatic retry logic:
import org.springframework.retry.annotation.Backoff;
import org.springframework.retry.annotation.Retryable;
import org.springframework.scheduling.annotation.Async;
@Service
public class SendblueAsyncService {
@Autowired
private SendblueService sendblue;
@Async
@Retryable(
maxAttempts = 3,
backoff = @Backoff(delay = 1000, multiplier = 2)
)
public void sendMessageAsync(String number, String content) {
sendblue.sendMessage(number, content, null, null);
}
}Enable async and retry in your Spring Boot application:
@SpringBootApplication
@EnableAsync
@EnableRetry
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}This pattern ensures reliable delivery without blocking your main application threads. Failed messages are automatically retried with exponential backoff.
Error Handling
Handle Sendblue API errors gracefully with a try-catch and structured logging:
public Map<String, Object> sendMessageSafe(String number, String content) {
try {
return sendMessage(number, content, null, null);
} catch (HttpClientErrorException e) {
log.error("Sendblue client error: {} - {}",
e.getStatusCode(), e.getResponseBodyAsString());
return Map.of("error", e.getMessage());
} catch (HttpServerErrorException e) {
log.error("Sendblue server error: {}", e.getStatusCode());
return Map.of("error", "Server error, will retry");
} catch (Exception e) {
log.error("Unexpected error sending iMessage", e);
return Map.of("error", e.getMessage());
}
}Common error codes: 400 (invalid phone number or missing fields), 401 (invalid API credentials), 429 (rate limited). See the API documentation for the complete error reference.
Next Steps
You're now sending iMessages from Java and Spring Boot. Continue with:
- Full API documentation — Group messaging, typing indicators, reactions, and more
- Webhook integration guide — Handle delivery receipts and all 7 webhook types
- Build an AI agent — Connect an LLM to iMessage
- API comparison — See how Sendblue compares to Twilio and Vonage
Get your free API keys and start sending iMessages from Java today.
Ready to send your first iMessage?
Get API access in minutes. Free sandbox, no credit card required.