Asynchronous Real-Time Content Moderation System Using Amazon Bedrock and Spring Boot
Content moderation is a critical feature for any platform hosting user-generated content. In this blog, we'll explore how to build an Asynchronous Real-Time Content Moderation System using Amazon Bedrock, Amazon Comprehend, and Spring Boot. We'll cover the architecture, code implementation, and how these services work together to ensure secure and clean content.
This is the continuation to the blog Building a Robust Multi-Module Content Platform with Spring Boot and MongoDB
Overview of the System
This system uses two powerful AWS services:
The moderation system works asynchronously, allowing fast responses to users while performing content checks in the background.
Key Features:
Architecture
User Interaction:
Asynchronous Moderation:
Decision Making:
Posts are either approved (APPROVED) or rejected (REJECTED) based on the analysis results.
Database Update:
Implementation Details
AWS Bedrock Integration
ClaudeV2 Service
The ClaudeV2 class interacts with the Claude model hosted on Amazon Bedrock:
@Service
public class ClaudeV2 {
@Autowired
private BedrockRuntimeClient bedrockRuntimeClient;
public boolean moderateContent(String text) {
String moderationPrompt = """
You are a content moderation tool. Analyze the following text for abusive language, profanity, or negativity.
Respond with "true" if the content is clean, otherwise "false".
Text: """ + text;
String response = invoke(moderationPrompt, 0.5, 200);
return "true".equalsIgnoreCase(response.trim());
}
private String invoke(String prompt, double temperature, int maxTokens) {
try {
// Prepare request body
JSONObject requestBody = new JSONObject()
.put("prompt", "Human: " + prompt + " Assistant:")
.put("temperature", temperature)
.put("max_tokens_to_sample", maxTokens);
InvokeModelRequest request = InvokeModelRequest.builder()
.modelId(Constants.CLAUDE_MODEL_ID_V2)
.body(SdkBytes.fromUtf8String(requestBody.toString()))
.build();
InvokeModelResponse response = bedrockRuntimeClient.invokeModel(request);
return new JSONObject(response.body().asUtf8String()).getString("completion");
} catch (Exception e) {
throw new RuntimeException("Failed to invoke Claude model", e);
}
}
}
Amazon Comprehend Integration
ContentModerationByComprehendService
This service uses Amazon Comprehend's toxicity detection capabilities:
@Service
public class ContentModerationByComprehendServiceImpl implements ContentModerationByComprehendService {
@Autowired
private ComprehendClient comprehendClient;
@Async
public CompletableFuture<Boolean> moderateContentByComprehend(String content) {
try {
DetectToxicContentRequest request = DetectToxicContentRequest.builder()
.textSegments(Collections.singletonList(TextSegment.builder().text(content).build()))
.languageCode("en")
.build();
DetectToxicContentResponse response = comprehendClient.detectToxicContent(request);
boolean isToxic = response.resultList().stream()
.flatMap(result -> result.labels().stream())
.anyMatch(label -> label.score() > 0.5);
return CompletableFuture.completedFuture(!isToxic);
} catch (Exception e) {
return CompletableFuture.completedFuture(false);
}
}
}
Asynchronous Moderation Workflow
The moderation workflow is triggered during blog creation or updates:
Method: runModerationAsync
Recommended by LinkedIn
private void runModerationAsync(BlogPost blogPost) {
CompletableFuture.runAsync(() -> {
boolean isTitleClean = true;
boolean isContentClean = true;
try {
if (blogPost.getPendingTitle() != null) {
isTitleClean = moderationByComprehendService
.moderateContentByComprehend(blogPost.getPendingTitle()).join();
if (isTitleClean) {
blogPost.setTitle(blogPost.getPendingTitle());
blogPost.setPendingTitle(null);
}
}
if (blogPost.getPendingContent() != null) {
isContentClean = moderateContentByClaude
.moderateContentByClaude(blogPost.getPendingContent()).join();
if (isContentClean) {
blogPost.setContent(blogPost.getPendingContent());
blogPost.setPendingContent(null);
}
}
blogPost.setStatus(isTitleClean && isContentClean ? Status.APPROVED : Status.REJECTED);
blogPostRepository.save(blogPost);
} catch (Exception e) {
blogPost.setStatus(Status.REJECTED);
blogPostRepository.save(blogPost);
}
});
}
Integration in Blog Service
Create Blog Post
@Override
public BlogPostDto createBlogPost(BlogPostDto blogPostDto, Authentication authentication) {
AppUserDto author = userUtil.extractUserFromAuth(authentication);
BlogPost blogPost = blogPostMapper.toEntity(blogPostDto);
blogPost.setAuthor(author);
blogPost.setCreatedAt(LocalDateTime.now());
blogPost.setUpdatedAt(LocalDateTime.now());
blogPost.setPendingTitle(blogPostDto.getTitle());
blogPost.setPendingContent(blogPostDto.getContent());
blogPost.setStatus(Status.PENDING_MODERATION); // Set the initial status
// Save the blog post and initiate background moderation
BlogPost savedPost = blogPostRepository.save(blogPost);
runModerationAsync(savedPost);
return blogPostMapper.toDto(savedPost);
}
Update Blog Post
@Override
public BlogPostDto updateBlogPost(String id, BlogPostDto blogPostDto, Authentication authentication) {
AppUserDto author = userUtil.extractUserFromAuth(authentication);
BlogPost blogPost = blogPostRepository.findById(id)
.orElseThrow(() -> new RuntimeException("Blog post not found"));
if (!blogPost.getAuthor().getId().equals(author.getId())) {
throw new RuntimeException("Unauthorized to update this blog post");
}
blogPost.setPendingTitle(blogPostDto.getTitle());
blogPost.setPendingContent(blogPostDto.getContent());
blogPost.setStatus(Status.PENDING_MODERATION);
blogPostRepository.save(blogPost);
runModerationAsync(blogPost);
return blogPostMapper.toDto(blogPost);
}
Advantages of This System
Example API Workflows
Create a Blog Post
Initially Status is in PENDING_MODERATION as above . but after few seconds Status updated to APPROVED due to CompletableFuture Async using ClaudeV2 and ContentModerationByComprehendService and as below .
Lets use some negative word in content as below .
As per logs title got clean result by ContentModerationByComprehendService but content got Abusive result by ClaudeV2 .
and status got changed to REJECTED as below.
Conclusion
By leveraging Amazon Bedrock, Amazon Comprehend, and Spring Boot, this system ensures safe, high-quality user-generated content. The asynchronous architecture enhances user experience while maintaining a robust moderation process. This approach is scalable, secure, and can be adapted to include additional moderation models or services.
GitHub Repository: Access the full source code here .
References: