java.util.logging (JUL)
Built-in logging API included in Java standard library. Available without external dependencies, providing basic logging functionality. Equipped with standard features like log levels, handlers, and formatters. Suitable for small-scale applications.
Library
Java Util Logging (JUL)
Overview
Java Util Logging (JUL) is the official logging framework built into the Java standard library since Java 1.4. Provided through the java.util.logging package, it offers a lightweight solution for logging functionality without external dependencies. It provides flexible log control through a hierarchical structure of Logger, Handler, and Formatter components, supporting both configuration files and programmatic configuration. Despite its simple API, it includes features that meet basic logging requirements for enterprise applications.
Details
JUL has been provided as the standard logging solution for the Java platform since the Java 1.4 release in 2002, and continues to be used in many Java applications as of 2025. It offers basic yet comprehensive logging functionality including log level control through Logger hierarchy (SEVERE, WARNING, INFO, CONFIG, FINE, FINER, FINEST), output destination control through various Handlers (file, console, memory, socket, etc.), log format control through custom Formatters, and centralized management through configuration files (logging.properties). As part of the Java standard library, it provides high compatibility and stability.
Key Features
- Standard Library Integration: Built into the Java platform with no external dependencies
- Hierarchical Logger Structure: Logger hierarchy management corresponding to package hierarchy
- Seven Log Levels: SEVERE, WARNING, INFO, CONFIG, FINE, FINER, FINEST
- Various Handlers: Support for Console, File, Memory, Socket, Stream, and other output destinations
- Configuration File Management: Centralized configuration management through logging.properties
- Internationalization Support: Message internationalization using ResourceBundle
Advantages and Disadvantages
Advantages
- Easy integration as part of Java standard library with no external dependencies
- Seamless JVM integration and high compatibility with the Java platform
- Runtime log level changes possible through configuration files
- Fine-grained log control through Logger hierarchy
- Flexible output destination control through Handler combinations
- Lightweight with sufficient functionality for basic logging requirements
Disadvantages
- Limited functionality compared to specialized libraries like Log4j2 or Logback
- Configuration complexity and difficulty can be a barrier for beginners
- Performance inferior to other modern logging libraries
- Basic log rotation functionality with difficult advanced control
- Less community support and update frequency compared to other libraries
- Operational difficulties in large-scale systems due to insufficient functionality
Reference Pages
- Java SE 8 API - java.util.logging
- Java Logging Best Practices
- Oracle Java Documentation - Logging Overview
Code Examples
Basic Setup
import java.util.logging.Logger;
import java.util.logging.Level;
import java.util.logging.ConsoleHandler;
import java.util.logging.FileHandler;
import java.util.logging.SimpleFormatter;
public class BasicLoggingSetup {
// Get Logger based on class name
private static final Logger logger = Logger.getLogger(BasicLoggingSetup.class.getName());
public static void main(String[] args) {
try {
// Configure console handler
ConsoleHandler consoleHandler = new ConsoleHandler();
consoleHandler.setLevel(Level.ALL);
consoleHandler.setFormatter(new SimpleFormatter());
// Configure file handler
FileHandler fileHandler = new FileHandler("application.log", true);
fileHandler.setLevel(Level.INFO);
fileHandler.setFormatter(new SimpleFormatter());
// Add handlers to Logger
logger.addHandler(consoleHandler);
logger.addHandler(fileHandler);
logger.setLevel(Level.ALL);
// Disable propagation to parent Logger (prevent duplicate output)
logger.setUseParentHandlers(false);
logger.info("Java Util Logging initialized successfully");
} catch (Exception e) {
System.err.println("Logging configuration error: " + e.getMessage());
}
}
}
Basic Log Output
import java.util.logging.Logger;
import java.util.logging.Level;
public class BasicLogging {
private static final Logger logger = Logger.getLogger(BasicLogging.class.getName());
public void demonstrateBasicLogging() {
// Output using seven log levels
logger.severe("SEVERE: A fatal error has occurred");
logger.warning("WARNING: This is a warning message");
logger.info("INFO: This is a general information message");
logger.config("CONFIG: This is a configuration message");
logger.fine("FINE: This is detailed debug information");
logger.finer("FINER: This is more detailed debug information");
logger.finest("FINEST: This is the most detailed debug information");
// Level.OFF is not output
logger.log(Level.OFF, "This message will not be output");
// Parameterized logging
String userName = "John Doe";
int userId = 12345;
logger.log(Level.INFO, "User {0} (ID: {1}) has logged in",
new Object[]{userName, userId});
// Logging with exception information
try {
int result = 10 / 0;
} catch (ArithmeticException e) {
logger.log(Level.SEVERE, "Arithmetic error occurred", e);
}
// Conditional logging (performance optimization)
if (logger.isLoggable(Level.FINE)) {
String expensiveDebugInfo = calculateExpensiveDebugInfo();
logger.fine("Detailed debug information: " + expensiveDebugInfo);
}
}
private String calculateExpensiveDebugInfo() {
// Simulation of expensive processing
return "Detailed debug data";
}
}
Advanced Configuration (Custom Formatter and Handler)
import java.util.logging.*;
import java.io.IOException;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
// Custom formatter implementation
class CustomFormatter extends Formatter {
private static final DateTimeFormatter DATE_FORMAT =
DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss.SSS");
@Override
public String format(LogRecord record) {
StringBuilder sb = new StringBuilder();
// Timestamp
LocalDateTime dateTime = LocalDateTime.ofInstant(
record.getInstant(), java.time.ZoneId.systemDefault());
sb.append(dateTime.format(DATE_FORMAT));
// Log level
sb.append(" [").append(record.getLevel()).append("]");
// Thread information
sb.append(" [Thread-").append(record.getThreadID()).append("]");
// Class name and method name
if (record.getSourceClassName() != null) {
sb.append(" ").append(record.getSourceClassName());
if (record.getSourceMethodName() != null) {
sb.append(".").append(record.getSourceMethodName());
}
}
// Message
sb.append(" - ").append(formatMessage(record));
// Exception information
if (record.getThrown() != null) {
sb.append("\n");
java.io.StringWriter sw = new java.io.StringWriter();
java.io.PrintWriter pw = new java.io.PrintWriter(sw);
record.getThrown().printStackTrace(pw);
sb.append(sw.toString());
}
sb.append("\n");
return sb.toString();
}
}
// Custom filter implementation
class CustomFilter implements Filter {
@Override
public boolean isLoggable(LogRecord record) {
// Allow only logs from specific classes
String sourceClass = record.getSourceClassName();
if (sourceClass != null && sourceClass.contains("Security")) {
return record.getLevel().intValue() >= Level.WARNING.intValue();
}
return true;
}
}
// File handler with rotation functionality
class RotatingFileHandler extends FileHandler {
public RotatingFileHandler(String pattern, int limit, int count)
throws IOException, SecurityException {
super(pattern, limit, count, true);
}
}
public class AdvancedLoggingConfiguration {
private static final Logger logger = Logger.getLogger(
AdvancedLoggingConfiguration.class.getName());
public static void setupAdvancedLogging() {
try {
// Clear root logger configuration
Logger rootLogger = Logger.getLogger("");
Handler[] handlers = rootLogger.getHandlers();
for (Handler handler : handlers) {
rootLogger.removeHandler(handler);
}
// Custom console handler
ConsoleHandler consoleHandler = new ConsoleHandler();
consoleHandler.setLevel(Level.WARNING);
consoleHandler.setFormatter(new CustomFormatter());
consoleHandler.setFilter(new CustomFilter());
// File handler with rotation functionality
// 10MB limit, rotate up to 5 files
RotatingFileHandler fileHandler = new RotatingFileHandler(
"logs/app-%g.log", 10 * 1024 * 1024, 5);
fileHandler.setLevel(Level.ALL);
fileHandler.setFormatter(new CustomFormatter());
// Error-specific file handler
FileHandler errorHandler = new FileHandler("logs/error.log", true);
errorHandler.setLevel(Level.WARNING);
errorHandler.setFormatter(new CustomFormatter());
errorHandler.setFilter(record ->
record.getLevel().intValue() >= Level.WARNING.intValue());
// Add each handler to Logger
logger.addHandler(consoleHandler);
logger.addHandler(fileHandler);
logger.addHandler(errorHandler);
logger.setLevel(Level.ALL);
logger.setUseParentHandlers(false);
logger.info("Advanced logging configuration completed");
} catch (IOException e) {
System.err.println("Logging configuration error: " + e.getMessage());
}
}
public static void main(String[] args) {
setupAdvancedLogging();
// Test log output
logger.finest("Finest log");
logger.fine("Fine log");
logger.info("Info log");
logger.warning("Warning log");
logger.severe("Severe error log");
}
}
Error Handling and Configuration File Management
import java.util.logging.*;
import java.io.IOException;
import java.io.FileInputStream;
import java.util.Properties;
public class ConfigurationBasedLogging {
private static final Logger logger = Logger.getLogger(
ConfigurationBasedLogging.class.getName());
// Load logging configuration from configuration file
public static void loadLoggingConfiguration() {
try {
// Load logging.properties file
LogManager.getLogManager().readConfiguration(
new FileInputStream("config/logging.properties"));
logger.info("Logging configuration file loaded successfully");
} catch (IOException e) {
System.err.println("Failed to load logging configuration file: " + e.getMessage());
// Fallback configuration
setupFallbackLogging();
} catch (SecurityException e) {
System.err.println("Logging configuration security error: " + e.getMessage());
}
}
// Fallback configuration
private static void setupFallbackLogging() {
try {
ConsoleHandler consoleHandler = new ConsoleHandler();
consoleHandler.setLevel(Level.INFO);
consoleHandler.setFormatter(new SimpleFormatter());
logger.addHandler(consoleHandler);
logger.setLevel(Level.INFO);
logger.setUseParentHandlers(false);
logger.warning("Fallback logging configuration applied");
} catch (Exception e) {
System.err.println("Fallback configuration error: " + e.getMessage());
}
}
// Application-specific log methods
public static class ApplicationLogger {
private final Logger appLogger;
public ApplicationLogger(String name) {
this.appLogger = Logger.getLogger(name);
}
public void logUserAction(String userId, String action) {
appLogger.info(String.format("USER_ACTION: %s performed %s", userId, action));
}
public void logSystemEvent(String event, Object... params) {
appLogger.log(Level.INFO, "SYSTEM_EVENT: " + event, params);
}
public void logError(String component, String errorMessage, Throwable throwable) {
appLogger.log(Level.SEVERE,
String.format("ERROR in %s: %s", component, errorMessage), throwable);
}
public void logPerformanceMetric(String metricName, double value) {
if (appLogger.isLoggable(Level.CONFIG)) {
appLogger.config(String.format("METRIC: %s = %.3f", metricName, value));
}
}
public void logSecurityEvent(String event, String details) {
Logger securityLogger = Logger.getLogger("SECURITY." + appLogger.getName());
securityLogger.warning(String.format("SECURITY_EVENT: %s - %s", event, details));
}
}
public static void main(String[] args) {
// Load from configuration file
loadLoggingConfiguration();
// Application logger usage example
ApplicationLogger appLogger = new ApplicationLogger("MyApplication");
try {
appLogger.logSystemEvent("Application started");
appLogger.logUserAction("user123", "login");
appLogger.logPerformanceMetric("response_time", 0.125);
// Some business logic
performBusinessLogic();
} catch (Exception e) {
appLogger.logError("MainApplication", "Unexpected error", e);
} finally {
appLogger.logSystemEvent("Application shutdown");
}
}
private static void performBusinessLogic() throws Exception {
// Business logic simulation
if (Math.random() > 0.7) {
throw new Exception("Random business exception");
}
}
}
Practical Example (Web Application Integration)
import java.util.logging.*;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
// logging.properties configuration file example (as comments)
/*
# logging.properties
handlers=java.util.logging.FileHandler, java.util.logging.ConsoleHandler
# Root logger configuration
.level=INFO
# File handler configuration
java.util.logging.FileHandler.pattern=logs/webapp_%g.log
java.util.logging.FileHandler.limit=50000000
java.util.logging.FileHandler.count=5
java.util.logging.FileHandler.formatter=java.util.logging.SimpleFormatter
java.util.logging.FileHandler.level=ALL
# Console handler configuration
java.util.logging.ConsoleHandler.level=WARNING
java.util.logging.ConsoleHandler.formatter=java.util.logging.SimpleFormatter
# Individual logger configuration
com.example.webapp.level=FINE
com.example.webapp.security.level=ALL
*/
public class WebApplicationLogging {
// Loggers for each component
private static final Logger ACCESS_LOGGER = Logger.getLogger("webapp.access");
private static final Logger SECURITY_LOGGER = Logger.getLogger("webapp.security");
private static final Logger PERFORMANCE_LOGGER = Logger.getLogger("webapp.performance");
private static final Logger ERROR_LOGGER = Logger.getLogger("webapp.error");
static {
// Perform log configuration in static initialization
initializeLogging();
}
private static void initializeLogging() {
try {
// Load log configuration from system properties
String configFile = System.getProperty("java.util.logging.config.file");
if (configFile != null) {
LogManager.getLogManager().readConfiguration(
new FileInputStream(configFile));
}
} catch (Exception e) {
System.err.println("Log configuration initialization error: " + e.getMessage());
}
}
// HTTP request logging
public static void logHttpRequest(HttpServletRequest request,
HttpServletResponse response,
long processingTime) {
String clientIp = getClientIpAddress(request);
String method = request.getMethod();
String uri = request.getRequestURI();
String query = request.getQueryString();
int statusCode = response.getStatus();
String logMessage = String.format(
"HTTP_REQUEST: %s %s %s%s %d %dms",
clientIp, method, uri,
(query != null ? "?" + query : ""),
statusCode, processingTime
);
// Access log
ACCESS_LOGGER.info(logMessage);
// Performance warning
if (processingTime > 1000) {
PERFORMANCE_LOGGER.warning(
String.format("Slow request detected: %s took %dms", uri, processingTime));
}
// Error log
if (statusCode >= 500) {
ERROR_LOGGER.severe(String.format("Server error: %d for %s", statusCode, uri));
} else if (statusCode >= 400) {
ERROR_LOGGER.warning(String.format("Client error: %d for %s", statusCode, uri));
}
}
// Security event logging
public static void logSecurityEvent(String eventType, String details,
HttpServletRequest request) {
String clientIp = getClientIpAddress(request);
String userAgent = request.getHeader("User-Agent");
String sessionId = request.getSession(false) != null ?
request.getSession().getId() : "no-session";
String logMessage = String.format(
"SECURITY_EVENT: %s | IP: %s | Session: %s | UA: %s | Details: %s",
eventType, clientIp, sessionId, userAgent, details
);
SECURITY_LOGGER.warning(logMessage);
}
// Error logging
public static void logApplicationError(String component, String operation,
Throwable error, Object... context) {
StringBuilder contextStr = new StringBuilder();
for (int i = 0; i < context.length; i += 2) {
if (i + 1 < context.length) {
contextStr.append(context[i]).append("=").append(context[i + 1]).append(" ");
}
}
String logMessage = String.format(
"APP_ERROR: [%s.%s] %s | Context: %s",
component, operation, error.getMessage(), contextStr.toString().trim()
);
ERROR_LOGGER.log(Level.SEVERE, logMessage, error);
}
// Business event logging
public static void logBusinessEvent(String eventType, String userId,
String details) {
String logMessage = String.format(
"BUSINESS_EVENT: %s | User: %s | Details: %s",
eventType, userId, details
);
ACCESS_LOGGER.info(logMessage);
}
// Performance metrics logging
public static void logPerformanceMetric(String metricName, double value,
String unit) {
if (PERFORMANCE_LOGGER.isLoggable(Level.CONFIG)) {
String logMessage = String.format(
"PERFORMANCE_METRIC: %s = %.3f %s",
metricName, value, unit
);
PERFORMANCE_LOGGER.config(logMessage);
}
}
private static String getClientIpAddress(HttpServletRequest request) {
String xForwardedFor = request.getHeader("X-Forwarded-For");
if (xForwardedFor != null && !xForwardedFor.isEmpty()) {
return xForwardedFor.split(",")[0].trim();
}
String xRealIp = request.getHeader("X-Real-IP");
if (xRealIp != null && !xRealIp.isEmpty()) {
return xRealIp;
}
return request.getRemoteAddr();
}
// Usage example (servlet usage)
public static void main(String[] args) {
// Sample usage example
// System startup log
ACCESS_LOGGER.info("SYSTEM_STARTUP: Web application started successfully");
// Business event
logBusinessEvent("USER_REGISTRATION", "user123", "New user registration completed");
// Security event (mock)
// logSecurityEvent("INVALID_LOGIN", "5 consecutive failures", mockRequest);
// Performance metrics
logPerformanceMetric("database_connection_pool_usage", 0.75, "ratio");
// Error event
try {
throw new RuntimeException("Database connection error");
} catch (Exception e) {
logApplicationError("DatabaseService", "getConnection", e,
"database", "primary", "retry_count", 3);
}
// System shutdown log
ACCESS_LOGGER.info("SYSTEM_SHUTDOWN: Web application shutting down gracefully");
}
}