Skip to content

Commit 08985a3

Browse files
authored
GH-4012: Fix ContainerProperties & ConsumerProperties toString() for StringBuilder
Fixes: #4012 * Improve toString() performance with StringBuilder This commit improves the performance of toString() methods in ContainerProperties and ConsumerProperties classes by replacing string concatenations with StringBuilder usage. Motivation: The ContainerProperties.toString() method is frequently used in Spring Boot applications for debugging Kafka container configuration issues. With 40+ properties being concatenated using the + operator, the original implementation created excessive temporary String objects, leading to: - Memory allocation overhead - Performance degradation in logging-heavy scenarios - Increased GC pressure Changes: ContainerProperties: - Replaced string concatenation with StringBuilder in toString() method - Added helper methods for consistent formatting: - appendProperty(): for regular non-null properties - appendEnabledProperty(): for properties with "enabled/not enabled" formatting - Improved organization with logical property grouping and descriptive comments - Enhanced maintainability through reduced code duplication ConsumerProperties: - Updated renderProperties() method to use StringBuilder instead of string concatenations - Modified renderTopics() to accept StringBuilder parameter for efficiency - Applied ContainerProperties improvements Performance Impact: - Reduced memory allocations during toString() operations - Improved performance in logging-heavy scenarios - Better scalability for applications with frequent container configuration logging Backward Compatibility: - No breaking changes - Output format unchanged - All existing functionality preserved * Skip null fields in toString() output Omit properties with null values from the toString() output by updating the appendProperty method. This improves readability and avoids clutter in logs by excluding unset configuration fields. * Apply Spring code style using spring-framework.xml Reformatted ContainerProperties.java to comply with the project's Spring-specific IntelliJ code style configuration. This ensures consistency and passes local style checks. * Remove trailing whitespace to fix checkstyle violations Removed trailing spaces from lines 517, 533, 535, 542, and 546 in ConsumerProperties.java. This change ensures that `./gradlew check` passes successfully, addressing the reviewer's request for code style compliance. Signed-off-by: Choi Wang Gyu <[email protected]> **Auto-cherry-pick to `3.3.x`**
1 parent 052bed7 commit 08985a3

File tree

2 files changed

+137
-73
lines changed

2 files changed

+137
-73
lines changed

spring-kafka/src/main/java/org/springframework/kafka/listener/ConsumerProperties.java

Lines changed: 47 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@
3636
*
3737
* @author Gary Russell
3838
* @author Sagnhyeok An
39+
* @author Choi Wang Gyu
3940
* @since 2.3
4041
*
4142
*/
@@ -510,29 +511,52 @@ public String toString() {
510511
}
511512

512513
protected final String renderProperties() {
513-
return renderTopics()
514-
+ "\n pollTimeout=" + this.pollTimeout
515-
+ (this.groupId != null ? "\n groupId=" + this.groupId : "")
516-
+ (StringUtils.hasText(this.clientId) ? "\n clientId=" + this.clientId : "")
517-
+ (this.consumerRebalanceListener != null
518-
? "\n consumerRebalanceListener=" + this.consumerRebalanceListener
519-
: "")
520-
+ (this.commitCallback != null ? "\n commitCallback=" + this.commitCallback : "")
521-
+ (this.offsetAndMetadataProvider != null ? "\n offsetAndMetadataProvider=" + this.offsetAndMetadataProvider : "")
522-
+ "\n syncCommits=" + this.syncCommits
523-
+ (this.syncCommitTimeout != null ? "\n syncCommitTimeout=" + this.syncCommitTimeout : "")
524-
+ (!this.kafkaConsumerProperties.isEmpty() ? "\n properties=" + this.kafkaConsumerProperties : "")
525-
+ "\n authExceptionRetryInterval=" + this.authExceptionRetryInterval
526-
+ "\n commitRetries=" + this.commitRetries
527-
+ "\n fixTxOffsets" + this.fixTxOffsets;
528-
}
529-
530-
private String renderTopics() {
531-
return (this.topics != null ? "\n topics=" + Arrays.toString(this.topics) : "")
532-
+ (this.topicPattern != null ? "\n topicPattern=" + this.topicPattern : "")
533-
+ (this.topicPartitions != null
534-
? "\n topicPartitions=" + Arrays.toString(this.topicPartitions)
535-
: "");
514+
StringBuilder sb = new StringBuilder();
515+
renderTopics(sb);
516+
sb.append("\n pollTimeout=").append(this.pollTimeout);
517+
518+
if (this.groupId != null) {
519+
sb.append("\n groupId=").append(this.groupId);
520+
}
521+
if (StringUtils.hasText(this.clientId)) {
522+
sb.append("\n clientId=").append(this.clientId);
523+
}
524+
if (this.consumerRebalanceListener != null) {
525+
sb.append("\n consumerRebalanceListener=").append(this.consumerRebalanceListener);
526+
}
527+
if (this.commitCallback != null) {
528+
sb.append("\n commitCallback=").append(this.commitCallback);
529+
}
530+
if (this.offsetAndMetadataProvider != null) {
531+
sb.append("\n offsetAndMetadataProvider=").append(this.offsetAndMetadataProvider);
532+
}
533+
534+
sb.append("\n syncCommits=").append(this.syncCommits);
535+
536+
if (this.syncCommitTimeout != null) {
537+
sb.append("\n syncCommitTimeout=").append(this.syncCommitTimeout);
538+
}
539+
if (!this.kafkaConsumerProperties.isEmpty()) {
540+
sb.append("\n properties=").append(this.kafkaConsumerProperties);
541+
}
542+
543+
sb.append("\n authExceptionRetryInterval=").append(this.authExceptionRetryInterval);
544+
sb.append("\n commitRetries=").append(this.commitRetries);
545+
sb.append("\n fixTxOffsets=").append(this.fixTxOffsets);
546+
547+
return sb.toString();
548+
}
549+
550+
private void renderTopics(StringBuilder sb) {
551+
if (this.topics != null) {
552+
sb.append("\n topics=").append(Arrays.toString(this.topics));
553+
}
554+
if (this.topicPattern != null) {
555+
sb.append("\n topicPattern=").append(this.topicPattern);
556+
}
557+
if (this.topicPartitions != null) {
558+
sb.append("\n topicPartitions=").append(Arrays.toString(this.topicPartitions));
559+
}
536560
}
537561

538562
}

spring-kafka/src/main/java/org/springframework/kafka/listener/ContainerProperties.java

Lines changed: 90 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,7 @@
5454
* @author Lukasz Kaminski
5555
* @author Kyuhyeok Park
5656
* @author Wang Zhiyang
57+
* @author Choi Wang Gyu
5758
*/
5859
public class ContainerProperties extends ConsumerProperties {
5960

@@ -1116,56 +1117,95 @@ public void setRecordObservationsInBatch(boolean recordObservationsInBatch) {
11161117

11171118
@Override
11181119
public String toString() {
1119-
return "ContainerProperties ["
1120-
+ renderProperties()
1121-
+ "\n ackMode=" + this.ackMode
1122-
+ "\n ackCount=" + this.ackCount
1123-
+ "\n ackTime=" + this.ackTime
1124-
+ "\n consumerStartTimeout=" + this.consumerStartTimeout
1125-
+ "\n messageListener=" + this.messageListener
1126-
+ (this.listenerTaskExecutor != null
1127-
? "\n listenerTaskExecutor=" + this.listenerTaskExecutor
1128-
: "")
1129-
+ "\n shutdownTimeout=" + this.shutdownTimeout
1130-
+ "\n idleEventInterval="
1131-
+ (this.idleEventInterval == null ? "not enabled" : this.idleEventInterval)
1132-
+ "\n idlePartitionEventInterval="
1133-
+ (this.idlePartitionEventInterval == null ? "not enabled" : this.idlePartitionEventInterval)
1134-
+ (this.transactionManager != null
1135-
? "\n transactionManager=" + this.transactionManager
1136-
: "")
1137-
+ (this.kafkaAwareTransactionManager != null
1138-
? "\n kafkaAwareTransactionManager=" + this.kafkaAwareTransactionManager
1139-
: "")
1140-
+ "\n monitorInterval=" + this.monitorInterval
1141-
+ (this.scheduler != null ? "\n scheduler=" + this.scheduler : "")
1142-
+ "\n noPollThreshold=" + this.noPollThreshold
1143-
+ "\n pauseImmediate=" + this.pauseImmediate
1144-
+ "\n pollTimeoutWhilePaused=" + this.pollTimeoutWhilePaused
1145-
+ "\n subBatchPerPartition=" + this.subBatchPerPartition
1146-
+ "\n assignmentCommitOption=" + this.assignmentCommitOption
1147-
+ "\n deliveryAttemptHeader=" + this.deliveryAttemptHeader
1148-
+ "\n batchRecoverAfterRollback=" + this.batchRecoverAfterRollback
1149-
+ "\n eosMode=" + this.eosMode
1150-
+ "\n transactionDefinition=" + this.transactionDefinition
1151-
+ "\n stopContainerWhenFenced=" + this.stopContainerWhenFenced
1152-
+ "\n stopImmediate=" + this.stopImmediate
1153-
+ "\n asyncAcks=" + this.asyncAcks
1154-
+ "\n logContainerConfig=" + this.logContainerConfig
1155-
+ "\n missingTopicsFatal=" + this.missingTopicsFatal
1156-
+ "\n idleBeforeDataMultiplier=" + this.idleBeforeDataMultiplier
1157-
+ "\n idleBetweenPolls=" + this.idleBetweenPolls
1158-
+ "\n micrometerEnabled=" + this.micrometerEnabled
1159-
+ "\n observationEnabled=" + this.observationEnabled
1160-
+ (this.observationConvention != null
1161-
? "\n observationConvention=" + this.observationConvention
1162-
: "")
1163-
+ (this.observationRegistry != null
1164-
? "\n observationRegistry=" + this.observationRegistry
1165-
: "")
1166-
+ "\n restartAfterAuthExceptions=" + this.restartAfterAuthExceptions
1167-
+ "\n recordObservationsInBatch=" + this.recordObservationsInBatch
1168-
+ "\n]";
1120+
StringBuilder sb = new StringBuilder("ContainerProperties [");
1121+
sb.append(renderProperties());
1122+
1123+
// Core acknowledgment properties
1124+
appendProperty(sb, "ackMode", this.ackMode);
1125+
appendProperty(sb, "ackCount", this.ackCount);
1126+
appendProperty(sb, "ackTime", this.ackTime);
1127+
1128+
// Timeout and startup properties
1129+
appendProperty(sb, "consumerStartTimeout", this.consumerStartTimeout);
1130+
appendProperty(sb, "shutdownTimeout", this.shutdownTimeout);
1131+
1132+
// Listener configuration
1133+
appendProperty(sb, "messageListener", this.messageListener);
1134+
appendProperty(sb, "listenerTaskExecutor", this.listenerTaskExecutor);
1135+
1136+
// Idle event configuration
1137+
appendEnabledProperty(sb, "idleEventInterval", this.idleEventInterval);
1138+
appendEnabledProperty(sb, "idlePartitionEventInterval", this.idlePartitionEventInterval);
1139+
1140+
// Transaction management
1141+
appendProperty(sb, "transactionManager", this.transactionManager);
1142+
appendProperty(sb, "kafkaAwareTransactionManager", this.kafkaAwareTransactionManager);
1143+
appendProperty(sb, "transactionDefinition", this.transactionDefinition);
1144+
1145+
// Monitoring and scheduling
1146+
appendProperty(sb, "monitorInterval", this.monitorInterval);
1147+
appendProperty(sb, "scheduler", this.scheduler);
1148+
appendProperty(sb, "noPollThreshold", this.noPollThreshold);
1149+
1150+
// Container behavior flags
1151+
appendProperty(sb, "pauseImmediate", this.pauseImmediate);
1152+
appendProperty(sb, "stopImmediate", this.stopImmediate);
1153+
appendProperty(sb, "stopContainerWhenFenced", this.stopContainerWhenFenced);
1154+
appendProperty(sb, "asyncAcks", this.asyncAcks);
1155+
1156+
// Polling and partition configuration
1157+
appendProperty(sb, "pollTimeoutWhilePaused", this.pollTimeoutWhilePaused);
1158+
appendProperty(sb, "subBatchPerPartition", this.subBatchPerPartition);
1159+
appendProperty(sb, "assignmentCommitOption", this.assignmentCommitOption);
1160+
appendProperty(sb, "idleBetweenPolls", this.idleBetweenPolls);
1161+
1162+
// Header and recovery configuration
1163+
appendProperty(sb, "deliveryAttemptHeader", this.deliveryAttemptHeader);
1164+
appendProperty(sb, "batchRecoverAfterRollback", this.batchRecoverAfterRollback);
1165+
1166+
// Exactly-once semantics
1167+
appendProperty(sb, "eosMode", this.eosMode);
1168+
1169+
// Logging and error handling
1170+
appendProperty(sb, "logContainerConfig", this.logContainerConfig);
1171+
appendProperty(sb, "missingTopicsFatal", this.missingTopicsFatal);
1172+
appendProperty(sb, "restartAfterAuthExceptions", this.restartAfterAuthExceptions);
1173+
1174+
// Metrics and observation
1175+
appendProperty(sb, "micrometerEnabled", this.micrometerEnabled);
1176+
appendProperty(sb, "observationEnabled", this.observationEnabled);
1177+
appendProperty(sb, "recordObservationsInBatch", this.recordObservationsInBatch);
1178+
appendProperty(sb, "observationConvention", this.observationConvention);
1179+
appendProperty(sb, "observationRegistry", this.observationRegistry);
1180+
1181+
// Data multiplier
1182+
appendProperty(sb, "idleBeforeDataMultiplier", this.idleBeforeDataMultiplier);
1183+
1184+
sb.append("\n]");
1185+
return sb.toString();
1186+
}
1187+
1188+
/**
1189+
* Append a property to the StringBuilder with consistent formatting.
1190+
* @param sb the StringBuilder
1191+
* @param name the property name
1192+
* @param value the property value
1193+
*/
1194+
private void appendProperty(StringBuilder sb, String name, @Nullable Object value) {
1195+
if (value != null) {
1196+
sb.append("\n ").append(name).append("=").append(value);
1197+
}
1198+
}
1199+
1200+
/**
1201+
* Append a property with "enabled/not enabled" formatting for nullable values.
1202+
* @param sb the StringBuilder
1203+
* @param name the property name
1204+
* @param value the property value (nullable)
1205+
*/
1206+
private void appendEnabledProperty(StringBuilder sb, String name, @Nullable Object value) {
1207+
sb.append("\n ").append(name).append("=")
1208+
.append(value == null ? "not enabled" : value);
11691209
}
11701210

11711211
}

0 commit comments

Comments
 (0)