Boost Your Java Backend: Practical Coding Habits and Best Practices
This article shares a collection of practical Java backend coding habits—from using @ConfigurationProperties and @RequiredArgsConstructor, to modularizing code, handling exceptions, minimizing database queries, managing thread pools, naming caches, applying design patterns, and leveraging asynchronous tasks—to improve code quality and performance.
The author shares a set of practical coding habits for Java backend development.
Define Configuration File Information
Store variables in YAML files and use @ConfigurationProperties instead of @Value.
@Data
// specify prefix
@ConfigurationProperties(prefix = "developer")
@Component
public class DeveloperProperty {
private String name;
private String website;
private String qq;
private String phoneNumber;
}Inject the bean where needed:
@RestController
@RequiredArgsConstructor
public class PropertyController {
private final DeveloperProperty developerProperty;
@GetMapping("/property")
public Object index() {
return developerProperty.getName();
}
}Use @RequiredArgsConstructor Instead of @Autowired
Constructor injection is the recommended way to inject beans.
Code Modularization
Keep each method under 50 lines, split logic into small, reusable units.
Throw Exceptions Instead of Returning Errors
Prefer throwing exceptions over returning error codes.
Reduce Unnecessary DB Queries
Avoid extra queries; delete only services that are offline or not yet launched.
Avoid Returning null
Prevent unnecessary NullPointerExceptions when calling methods elsewhere.
if else
Limit the number of if else if branches; consider the Strategy pattern.
Reduce Controller Business Logic
Move business logic to the service layer for better maintainability and readability.
Leverage IDE (IntelliJ IDEA)
IDE can suggest improvements, such as replacing anonymous classes with lambda expressions.
Read Source Code
Study high‑star open‑source projects on GitHub to learn design ideas and advanced APIs.
Design Patterns
Apply the 23 common design patterns to write clean, maintainable code.
Embrace New Knowledge
Junior developers should explore topics beyond daily CRUD, practice demos of more challenging concepts.
Fundamental Issues
Map traversal examples:
HashMap<String, String> map = new HashMap<>();
map.put("name", "du");
for (String key : map.keySet()) {
String value = map.get(key);
}
map.forEach((k, v) -> {
// ...
});
// Recommended
for (Map.Entry<String, String> entry : map.entrySet()) {
// ...
}Optional null‑check example:
public List<CatalogueTreeNode> getChild(String pid) {
if (V.isEmpty(pid)) {
pid = BasicDic.TEMPORARY_DIRECTORY_ROOT;
}
CatalogueTreeNode node = treeNodeMap.get(pid);
return Optional.ofNullable(node)
.map(CatalogueTreeNode::getChild)
.orElse(Collections.emptyList());
}Recursion tip: for large data sets, pass reusable objects as method parameters instead of creating new ones inside recursion.
Check Element Existence
Use HashSet for O(1) lookups rather than List.
ArrayList<String> list = new ArrayList<>();
// check if "a" exists
for (int i = 0; i < list.size(); i++) {
if ("a".equals(elementData[i]))
return i;
} HashSet<String> set = new HashSet<>();
// check if "a" exists
int index = hash(a);
return getNode(index) != null;Unified Thread Pool Management
Create a shared ThreadPoolExecutor with consistent settings.
private static volatile ThreadPoolExecutor threadPoolExecutor = null;
private static final int CORE_POOL_SIZE = 0;
private static final int MAX_MUM_POOL_SIZE = 1000;
private static final long KEEP_ALIVE_TIME = 2;
private static final TimeUnit UNIT = TimeUnit.MINUTES;
private static final int CAPACITY = 1;
private static final RejectedExecutionHandler HANDLER = new ThreadPoolExecutor.CallerRunsPolicy();
private static final BasicThreadFactory factory = new BasicThreadFactory.Builder()
.namingPattern("aiserviceplatform-util-thread-pool-%d").build();
public static ThreadPoolExecutor getInstance() {
if (threadPoolExecutor == null) {
synchronized (ThreadPoolFactory.class) {
if (threadPoolExecutor == null) {
threadPoolExecutor = new ThreadPoolExecutor(
CORE_POOL_SIZE, MAX_MUM_POOL_SIZE, KEEP_ALIVE_TIME, UNIT,
new ArrayBlockingQueue<>(CAPACITY), factory, HANDLER);
}
}
}
return threadPoolExecutor;
}Bulk Data Synchronization
When syncing large tables, use temporary tables to avoid locking the main table.
String realTableName = ...;
String tempTableName = realTableName + "_temp";
createTable(tempTableName); // create temp table
boolean flag = sync(tempTableName); // sync data
if (flag) {
dropTable(realTableName);
alterTableName(realTableName, tempTableName); // rename temp to real
} else {
dropTable(tempTableName);
}Interface Parameters
Prefer using Collection<T> for method arguments to accept any collection type.
Collection<String> getNodeIds(Collection<String> ids);Lock Granularity
Prefer fine‑grained locks such as ReentrantLock or ReentrantReadWriteLock over large synchronized blocks.
Cache Naming
Use short, hierarchical names separated by colons, e.g., module:submodule:cache.
@Cacheable
Configure cache TTL when using @Cacheable.
@Bean
public CacheManager cacheManager(RedisConnectionFactory factory) {
return new RedisCacheManager(
RedisCacheWriter.lockingRedisCacheWriter(factory),
this.getRedisCacheConfigurationWithTtl(1),
this.getRedisCacheConfigurationMap()
);
}
private RedisCacheConfiguration getRedisCacheConfigurationWithTtl(Integer hour) {
Jackson2JsonRedisSerializer<Object> serializer = new Jackson2JsonRedisSerializer<>(Object.class);
ObjectMapper om = new ObjectMapper();
om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
om.activateDefaultTyping(LaissezFaireSubTypeValidator.instance, ObjectMapper.DefaultTyping.NON_FINAL);
serializer.setObjectMapper(om);
return RedisCacheConfiguration.defaultCacheConfig()
.serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(serializer))
.entryTtl(Duration.ofHours(hour));
}
public static final String REGION_LIST_BY_CODE_CACHE_KEY = "region:list";
public static final String REGION_NAME_BY_CODE_CACHE_KEY = "region:name";
private Map<String, RedisCacheConfiguration> getRedisCacheConfigurationMap() {
Map<String, RedisCacheConfiguration> map = new HashMap<>();
map.put(REGION_LIST_BY_CODE_CACHE_KEY, getRedisCacheConfigurationWithTtl(24));
map.put(REGION_NAME_BY_CODE_CACHE_KEY, getRedisCacheConfigurationWithTtl(120));
// other cache configurations ...
return map;
}Asynchronous Tasks
Offload time‑consuming operations to asynchronous tasks or message queues to improve response speed.
------------ END ------------
Signed-in readers can open the original source through BestHub's protected redirect.
This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactand we will review it promptly.
Java Interview Crash Guide
Dedicated to sharing Java interview Q&A; follow and reply "java" to receive a free premium Java interview guide.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
