Java in the Real World
Moving from theory to practice requires knowing the best practices for structuring professional projects: logging, configuration, environment management.
What You'll Learn
- Professional project structure
- Logging with SLF4J and Logback
- Externalized configuration
- Environment management (dev, test, prod)
Project Structure
my-project/
├── src/
│ ├── main/
│ │ ├── java/
│ │ │ └── com/example/
│ │ │ ├── Application.java
│ │ │ ├── config/
│ │ │ ├── controller/
│ │ │ ├── service/
│ │ │ ├── repository/
│ │ │ ├── model/
│ │ │ └── util/
│ │ └── resources/
│ │ ├── application.properties
│ │ ├── application-dev.properties
│ │ ├── application-prod.properties
│ │ └── logback.xml
│ └── test/
│ ├── java/
│ └── resources/
├── pom.xml (or build.gradle)
└── README.md
Logging
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class UserService {
private static final Logger logger = LoggerFactory.getLogger(UserService.class);
public User findUser(Long id) {
logger.debug("Searching for user with ID: {}", id);
try {
User user = repository.findById(id);
logger.info("User found: {}", user.getName());
return user;
} catch (UserNotFoundException e) {
logger.warn("User not found: {}", id);
throw e;
} catch (Exception e) {
logger.error("Error while searching for user", e);
throw new ServiceException("Internal error", e);
}
}
}
<?xml version="1.0" encoding="UTF-8"?>
<configuration>
<appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<pattern>%d{HH:mm:ss} [%thread] %-5level %logger{36} - %msg%n</pattern>
</encoder>
</appender>
<appender name="FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
<file>logs/app.log</file>
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<fileNamePattern>logs/app.%d{yyyy-MM-dd}.log</fileNamePattern>
<maxHistory>30</maxHistory>
</rollingPolicy>
<encoder>
<pattern>%d [%thread] %-5level %logger - %msg%n</pattern>
</encoder>
</appender>
<root level="INFO">
<appender-ref ref="CONSOLE" />
<appender-ref ref="FILE" />
</root>
<logger name="com.example" level="DEBUG" />
</configuration>
Configuration
# Database
db.url=jdbc:postgresql://localhost:5432/mydb
db.username=user
db.password=password
db.pool.size=10
# API
api.timeout.ms=5000
api.retry.count=3
# Feature flags
feature.newUi.enabled=true
import java.io.*;
import java.util.Properties;
public class ConfigurationManager {
private static final Properties props = new Properties();
static {
String env = System.getProperty("app.env", "dev");
String filename = "application-" + env + ".properties";
try (InputStream is = ConfigurationManager.class
.getClassLoader().getResourceAsStream(filename)) {
if (is != null) {
props.load(is);
}
} catch (IOException e) {
throw new RuntimeException("Unable to load configuration", e);
}
}
public static String get(String key) {
return props.getProperty(key);
}
public static int getInt(String key, int defaultValue) {
String value = props.getProperty(key);
return value != null ? Integer.parseInt(value) : defaultValue;
}
public static boolean getBoolean(String key) {
return Boolean.parseBoolean(props.getProperty(key));
}
}
// Usage
String dbUrl = ConfigurationManager.get("db.url");
int timeout = ConfigurationManager.getInt("api.timeout.ms", 3000);
Best Practices
Rules for Professional Projects
- Separate configuration from code: properties files, environment variables
- Log in a structured way: appropriate levels, no System.out
- Use architectural layers: controller, service, repository
- Automated testing: unit, integration, e2e
- Document: README, Javadoc for public APIs
- Handle exceptions: centralized, with logging







