How to Use a Weather API in Java

Java applications can retrieve historical weather, current conditions, and forecast data directly from the Visual Crossing Timeline Weather API.

In this tutorial, we’ll use Java’s built-in HttpClient to send Weather API requests and parse the returned JSON.

The Visual Crossing Weather API provides programmatic access to historical, current, and forecast weather data through a single Timeline Weather API endpoint.

If you want to explore or download weather datasets without writing code, see Visual Crossing Weather Data.

To follow the examples, sign up for a free Visual Crossing account and obtain your Weather API key.

For the complete API reference, see the Timeline Weather API documentation.

What we’ll build

We’ll create a Java application that:

  1. Builds a Timeline Weather API URL.
  2. Retrieves weather using Java’s built-in HttpClient.
  3. Checks HTTP responses for errors.
  4. Retrieves forecast weather.
  5. Retrieves historical weather for a date or date range.
  6. Adds current conditions and hourly weather.
  7. Limits the returned data using include and elements.
  8. Parses the returned JSON.

The examples use modern Java and do not require a third-party HTTP library.

Requirements

The examples in this article use Java 17 or later.

Check your installed Java version with:

java --version

The built-in Java HTTP client is available through:

java.net.http.HttpClient
java.net.http.HttpRequest
java.net.http.HttpResponse

For JSON parsing, we’ll show a complete example using Jackson later in the article.

The Weather API itself simply returns JSON, so you can use whichever JSON library your application already uses.

Get your Weather API key

Every Visual Crossing Weather API request requires an API key.

Rather than placing your key directly in the source code, read it from an environment variable:

String apiKey =
    System.getenv("VISUAL_CROSSING_API_KEY");

if (apiKey == null || apiKey.isBlank()) {
    throw new IllegalStateException(
        "VISUAL_CROSSING_API_KEY is not set"
    );
}

On Linux or macOS:

export VISUAL_CROSSING_API_KEY="YOUR_API_KEY"

On Windows PowerShell:

$env:VISUAL_CROSSING_API_KEY="YOUR_API_KEY"

Visual Crossing plans include usage and request limits, so automated applications should operate within the limits of the account plan being used.

If you’re new to the API, see Getting Started with the Weather API.

Understand the Timeline Weather API URL

The Timeline Weather API uses this basic structure:

/timeline/[location]/[date1]/[date2]

Only the location is required.

For example, a forecast request for London uses:

/timeline/London%2CUK

A historical request for July 1, 2026 uses:

/timeline/London%2CUK/2026-07-01

A historical date range uses:

/timeline/London%2CUK/2026-07-01/2026-07-07

When dates are omitted, the API returns the available forecast.

When one or two dates are supplied, the same Timeline endpoint returns weather for the requested period.

Build a Timeline Weather API URL

We’ll create a reusable method that accepts:

  • Location
  • Optional start date
  • Optional end date
  • Unit group
  • API key
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;

public static String buildWeatherUrl(
        String location,
        String startDate,
        String endDate,
        String unitGroup,
        String apiKey) {

    String baseUrl =
        "https://weather.visualcrossing.com/" +
        "VisualCrossingWebServices/rest/services/timeline/";

    String encodedLocation =
        URLEncoder.encode(
            location,
            StandardCharsets.UTF_8
        )
        .replace("+", "%20");

    StringBuilder url =
        new StringBuilder(
            baseUrl + encodedLocation
        );

    if (startDate != null &&
        !startDate.isBlank()) {

        url.append("/")
           .append(startDate);
    }

    if (endDate != null &&
        !endDate.isBlank()) {

        url.append("/")
           .append(endDate);
    }

    url.append("?unitGroup=")
       .append(
           URLEncoder.encode(
               unitGroup,
               StandardCharsets.UTF_8
           )
       )
       .append("&include=days")
       .append(
           "&elements=" +
           "datetime,tempmax,tempmin," +
           "precip,precipprob,conditions"
       )
       .append("&contentType=json")
       .append("&key=")
       .append(
           URLEncoder.encode(
               apiKey,
               StandardCharsets.UTF_8
           )
       );

    return url.toString();
}

For:

buildWeatherUrl(
    "London, UK",
    null,
    null,
    "metric",
    apiKey
);

the method creates a forecast request.

Supplying dates creates a historical-weather request using the same endpoint.

Create a reusable HttpClient

Modern Java includes HttpClient, so there is no need to add Apache HttpComponents simply to call the Weather API.

Create one reusable client:

import java.net.http.HttpClient;
import java.time.Duration;

private static final HttpClient HTTP_CLIENT =
    HttpClient.newBuilder()
        .connectTimeout(
            Duration.ofSeconds(10)
        )
        .build();

A single HttpClient can be reused for multiple requests.

Send the Weather API request

Create a method that sends the request and returns the JSON response:

import java.net.URI;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.time.Duration;

public static String requestWeather(
        String url)
        throws Exception {

    HttpRequest request =
        HttpRequest.newBuilder()
            .uri(
                URI.create(url)
            )
            .timeout(
                Duration.ofSeconds(20)
            )
            .GET()
            .build();

    HttpResponse<String> response =
        HTTP_CLIENT.send(
            request,
            HttpResponse.BodyHandlers.ofString()
        );

    if (response.statusCode() < 200 ||
        response.statusCode() >= 300) {

        throw new RuntimeException(
            "Weather API request failed (" +
            response.statusCode() +
            "): " +
            response.body()
        );
    }

    return response.body();
}

The request is sent using:

HTTP_CLIENT.send(...)

and the Weather API response is returned as a String.

Check HTTP errors before parsing JSON

Always inspect the HTTP status before trying to process the response as weather data.

The example checks:

if (response.statusCode() < 200 ||
    response.statusCode() >= 300) {

and includes the API response body in the error message:

throw new RuntimeException(
    "Weather API request failed (" +
    response.statusCode() +
    "): " +
    response.body()
);

This makes common problems much easier to diagnose, including:

  • Invalid API keys
  • Invalid locations
  • Invalid dates
  • Invalid parameters
  • Account usage limits
  • Network problems

Retrieve a weather forecast

You can now retrieve forecast weather using:

String url =
    buildWeatherUrl(
        "London, UK",
        null,
        null,
        "metric",
        apiKey
    );

String json =
    requestWeather(url);

System.out.println(json);

Because no dates are supplied, the Timeline Weather API returns the available forecast.

Retrieve historical weather

To retrieve a single historical date:

String url =
    buildWeatherUrl(
        "London, UK",
        "2026-07-01",
        null,
        "metric",
        apiKey
    );

For a date range:

String url =
    buildWeatherUrl(
        "London, UK",
        "2026-07-01",
        "2026-07-07",
        "metric",
        apiKey
    );

The same HTTP and JSON-processing code can be used for both historical and forecast weather.

This consistent request structure is one of the main advantages of the Timeline Weather API.

Complete Java Weather API request example

Here is the complete example so far:

import java.net.URI;
import java.net.URLEncoder;

import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;

import java.nio.charset.StandardCharsets;

import java.time.Duration;


public class WeatherExample {

    private static final HttpClient HTTP_CLIENT =
        HttpClient.newBuilder()
            .connectTimeout(
                Duration.ofSeconds(10)
            )
            .build();


    public static void main(
            String[] args)
            throws Exception {

        String apiKey =
            System.getenv(
                "VISUAL_CROSSING_API_KEY"
            );

        if (apiKey == null ||
            apiKey.isBlank()) {

            throw new IllegalStateException(
                "VISUAL_CROSSING_API_KEY is not set"
            );
        }


        String url =
            buildWeatherUrl(
                "London, UK",
                null,
                null,
                "metric",
                apiKey
            );


        String json =
            requestWeather(url);


        System.out.println(json);
    }


    public static String buildWeatherUrl(
            String location,
            String startDate,
            String endDate,
            String unitGroup,
            String apiKey) {

        String baseUrl =
            "https://weather.visualcrossing.com/" +
            "VisualCrossingWebServices/rest/services/" +
            "timeline/";


        String encodedLocation =
            URLEncoder.encode(
                location,
                StandardCharsets.UTF_8
            )
            .replace("+", "%20");


        StringBuilder url =
            new StringBuilder(
                baseUrl +
                encodedLocation
            );


        if (startDate != null &&
            !startDate.isBlank()) {

            url.append("/")
               .append(startDate);
        }


        if (endDate != null &&
            !endDate.isBlank()) {

            url.append("/")
               .append(endDate);
        }


        url.append("?unitGroup=")
           .append(
               URLEncoder.encode(
                   unitGroup,
                   StandardCharsets.UTF_8
               )
           )
           .append("&include=days")
           .append(
               "&elements=" +
               "datetime,tempmax,tempmin," +
               "precip,precipprob,conditions"
           )
           .append("&contentType=json")
           .append("&key=")
           .append(
               URLEncoder.encode(
                   apiKey,
                   StandardCharsets.UTF_8
               )
           );


        return url.toString();
    }


    public static String requestWeather(
            String url)
            throws Exception {

        HttpRequest request =
            HttpRequest.newBuilder()
                .uri(
                    URI.create(url)
                )
                .timeout(
                    Duration.ofSeconds(20)
                )
                .GET()
                .build();


        HttpResponse<String> response =
            HTTP_CLIENT.send(
                request,
                HttpResponse.BodyHandlers.ofString()
            );


        if (response.statusCode() < 200 ||
            response.statusCode() >= 300) {

            throw new RuntimeException(
                "Weather API request failed (" +
                response.statusCode() +
                "): " +
                response.body()
            );
        }


        return response.body();
    }
}

This example retrieves the JSON but does not yet convert it into Java objects.

Parse the Weather API JSON

The Java standard library does not include a general-purpose JSON object mapper.

If your application already uses a JSON library such as Jackson, Gson, or JSON-B, you can use that existing library.

For this example we’ll use Jackson.

With Maven, add:

<dependency>
    <groupId>com.fasterxml.jackson.core</groupId>
    <artifactId>jackson-databind</artifactId>
    <version>2.20.0</version>
</dependency>

If your project uses a dependency-management platform or framework such as Spring Boot, use the version managed by that platform rather than forcing a separate version.

Define Java classes for the Weather API response

We only need to define the Weather API fields our application uses.

import java.util.List;


public class WeatherResponse {

    public String resolvedAddress;

    public String timezone;

    public List<WeatherDay> days;
}

Create a daily weather class:

public class WeatherDay {

    public String datetime;

    public Double tempmax;

    public Double tempmin;

    public Double precip;

    public Double precipprob;

    public String conditions;
}

The field names correspond directly to Timeline Weather API JSON fields such as:

datetime
tempmax
tempmin
precip
precipprob
conditions

Jackson ignores additional JSON properties by default when configured appropriately.

Decode the JSON with Jackson

Create an ObjectMapper:

import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.ObjectMapper;


private static final ObjectMapper OBJECT_MAPPER =
    new ObjectMapper()
        .configure(
            DeserializationFeature
                .FAIL_ON_UNKNOWN_PROPERTIES,
            false
        );

Then decode the response:

WeatherResponse weather =
    OBJECT_MAPPER.readValue(
        json,
        WeatherResponse.class
    );

Daily weather data is available through:

weather.days

For example:

System.out.println(
    "Weather for " +
    weather.resolvedAddress
);


for (WeatherDay day :
     weather.days) {

    System.out.printf(
        "%s: %s, high %.1f, low %.1f%n",
        day.datetime,
        day.conditions,
        day.tempmax,
        day.tempmin
    );
}

Complete Java example with JSON parsing

The following example retrieves and parses forecast weather:

import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.ObjectMapper;

import java.net.URI;
import java.net.URLEncoder;

import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;

import java.nio.charset.StandardCharsets;

import java.time.Duration;

import java.util.List;


public class WeatherExample {

    private static final HttpClient HTTP_CLIENT =
        HttpClient.newBuilder()
            .connectTimeout(
                Duration.ofSeconds(10)
            )
            .build();


    private static final ObjectMapper OBJECT_MAPPER =
        new ObjectMapper()
            .configure(
                DeserializationFeature
                    .FAIL_ON_UNKNOWN_PROPERTIES,
                false
            );


    public static void main(
            String[] args)
            throws Exception {

        String apiKey =
            System.getenv(
                "VISUAL_CROSSING_API_KEY"
            );


        if (apiKey == null ||
            apiKey.isBlank()) {

            throw new IllegalStateException(
                "VISUAL_CROSSING_API_KEY is not set"
            );
        }


        String url =
            buildWeatherUrl(
                "London, UK",
                null,
                null,
                "metric",
                apiKey
            );


        String json =
            requestWeather(url);


        WeatherResponse weather =
            OBJECT_MAPPER.readValue(
                json,
                WeatherResponse.class
            );


        System.out.println(
            "Weather for " +
            weather.resolvedAddress
        );


        for (WeatherDay day :
             weather.days) {

            System.out.printf(
                "%s: %s, " +
                "high %.1f, " +
                "low %.1f, " +
                "precip probability %.0f%%%n",
                day.datetime,
                day.conditions,
                day.tempmax,
                day.tempmin,
                day.precipprob
            );
        }
    }


    public static String buildWeatherUrl(
            String location,
            String startDate,
            String endDate,
            String unitGroup,
            String apiKey) {

        String baseUrl =
            "https://weather.visualcrossing.com/" +
            "VisualCrossingWebServices/rest/services/" +
            "timeline/";


        String encodedLocation =
            URLEncoder.encode(
                location,
                StandardCharsets.UTF_8
            )
            .replace("+", "%20");


        StringBuilder url =
            new StringBuilder(
                baseUrl +
                encodedLocation
            );


        if (startDate != null &&
            !startDate.isBlank()) {

            url.append("/")
               .append(startDate);
        }


        if (endDate != null &&
            !endDate.isBlank()) {

            url.append("/")
               .append(endDate);
        }


        url.append("?unitGroup=")
           .append(
               URLEncoder.encode(
                   unitGroup,
                   StandardCharsets.UTF_8
               )
           )
           .append("&include=days")
           .append(
               "&elements=" +
               "datetime,tempmax,tempmin," +
               "precip,precipprob,conditions"
           )
           .append("&contentType=json")
           .append("&key=")
           .append(
               URLEncoder.encode(
                   apiKey,
                   StandardCharsets.UTF_8
               )
           );


        return url.toString();
    }


    public static String requestWeather(
            String url)
            throws Exception {

        HttpRequest request =
            HttpRequest.newBuilder()
                .uri(
                    URI.create(url)
                )
                .timeout(
                    Duration.ofSeconds(20)
                )
                .GET()
                .build();


        HttpResponse<String> response =
            HTTP_CLIENT.send(
                request,
                HttpResponse.BodyHandlers.ofString()
            );


        if (response.statusCode() < 200 ||
            response.statusCode() >= 300) {

            throw new RuntimeException(
                "Weather API request failed (" +
                response.statusCode() +
                "): " +
                response.body()
            );
        }


        return response.body();
    }


    public static class WeatherResponse {

        public String resolvedAddress;

        public String timezone;

        public List<WeatherDay> days;
    }


    public static class WeatherDay {

        public String datetime;

        public Double tempmax;

        public Double tempmin;

        public Double precip;

        public Double precipprob;

        public String conditions;
    }
}

Retrieve current conditions

The Timeline Weather API can return current conditions in the same response.

Change:

include=days

to:

include=current,days

and add the desired current-condition elements.

For example:

elements=datetime,temp,tempmax,tempmin,humidity,precip,precipprob,conditions

Add a current-conditions class:

public static class CurrentConditions {

    public String datetime;

    public Double temp;

    public Double humidity;

    public Double precip;

    public String conditions;
}

Then update WeatherResponse:

public static class WeatherResponse {

    public String resolvedAddress;

    public String timezone;

    public CurrentConditions currentConditions;

    public List<WeatherDay> days;
}

You can then access:

weather.currentConditions.temp
weather.currentConditions.humidity
weather.currentConditions.conditions

For example:

System.out.printf(
    "Current temperature: %.1f%n",
    weather.currentConditions.temp
);

System.out.println(
    "Conditions: " +
    weather.currentConditions.conditions
);

For more detail, see How to Get Current Weather Conditions from the Weather API.

Retrieve hourly weather

To retrieve hourly weather, request:

include=days,hours

and include hourly weather elements such as:

datetime,temp,feelslike,humidity,precip,precipprob,windspeed,conditions

Define an hourly class:

public static class WeatherHour {

    public String datetime;

    public Double temp;

    public Double feelslike;

    public Double humidity;

    public Double precip;

    public Double precipprob;

    public Double windspeed;

    public String conditions;
}

Then add an hourly list to WeatherDay:

public static class WeatherDay {

    public String datetime;

    public Double tempmax;

    public Double tempmin;

    public String conditions;

    public List<WeatherHour> hours;
}

You can then iterate through the hourly data:

if (!weather.days.isEmpty()) {

    for (WeatherHour hour :
         weather.days.get(0).hours) {

        System.out.printf(
            "%s: %.1f, %s%n",
            hour.datetime,
            hour.temp,
            hour.conditions
        );
    }
}

Request only the weather elements you need

The Timeline Weather API supports many weather variables.

Use the elements parameter to restrict the response to the fields your Java application needs.

For example:

elements=datetime,tempmax,tempmin,conditions

is sufficient for a simple daily forecast.

Other commonly used fields include:

temp
feelslike
humidity
dew
precip
precipprob
snow
snowdepth
windspeed
windgust
winddir
pressure
cloudcover
visibility
solarradiation
solarenergy
uvindex
conditions
icon

For the complete list, see the Timeline Weather API documentation.

Use US or metric units

The examples use:

unitGroup=metric

For US units:

unitGroup=us

The selected unit group controls units including:

  • Temperature
  • Precipitation
  • Wind speed
  • Visibility
  • Other weather measurements

You can simply pass another unit group to:

buildWeatherUrl(...)

For example:

String url =
    buildWeatherUrl(
        "New York, NY",
        null,
        null,
        "us",
        apiKey
    );

Make the location dynamic

A real application will usually receive the location from configuration, user input, a database, or another service.

For example:

String location =
    args.length > 0
        ? args[0]
        : "London, UK";

Then:

String url =
    buildWeatherUrl(
        location,
        null,
        null,
        "metric",
        apiKey
    );

The Timeline Weather API supports locations such as:

  • City and country
  • Full or partial address
  • Postal or ZIP code
  • Latitude and longitude
  • Supported weather-station identifiers

Location values should be URL encoded before being added to the request path.

Retrieve dynamic date ranges

Timeline also supports dynamic date values for many common workflows.

For example:

today
yesterday
last7days
last30days

A recent-weather request can therefore use:

String url =
    buildWeatherUrl(
        "London, UK",
        "last7days",
        null,
        "metric",
        apiKey
    );

This can be useful in scheduled or recurring applications where the required period moves forward automatically.

Asynchronous requests with HttpClient

HttpClient also supports asynchronous requests.

Instead of:

HTTP_CLIENT.send(...)

you can use:

HTTP_CLIENT.sendAsync(
    request,
    HttpResponse.BodyHandlers.ofString()
)
.thenApply(response -> {

    if (response.statusCode() < 200 ||
        response.statusCode() >= 300) {

        throw new RuntimeException(
            "Weather API request failed (" +
            response.statusCode() +
            "): " +
            response.body()
        );
    }

    return response.body();
});

This returns a:

CompletableFuture<String>

and can be useful in applications that already use asynchronous Java workflows.

For a simple command-line program, the synchronous send() example is easier to follow.

Use GET requests for standard Timeline queries

Standard Timeline Weather API requests use normal HTTP GET requests.

For example:

HttpRequest request =
    HttpRequest.newBuilder()
        .uri(
            URI.create(url)
        )
        .GET()
        .build();

This is sufficient for the normal location and date-range requests shown in this tutorial.

For specialized workflows involving larger request payloads or multiple locations, see the corresponding Visual Crossing documentation rather than converting a normal Timeline request to POST unnecessarily.

Handle network and API errors

There are two broad types of failures your application should handle.

Network errors

These include:

  • DNS problems
  • Connection failures
  • TLS failures
  • Connection timeouts
  • Read timeouts

These usually result in an exception from HttpClient.

Weather API errors

The Weather API can return an HTTP error response for reasons such as:

  • Invalid API key
  • Invalid location
  • Invalid dates
  • Invalid parameters
  • Account usage limits
  • Features unavailable on the account plan

The response status and body should both be logged when troubleshooting:

System.err.println(
    "HTTP status: " +
    response.statusCode()
);

System.err.println(
    response.body()
);

Do not try to parse an unsuccessful response as a normal Weather API JSON result.

Do not disable TLS certificate verification

A normal Java HTTPS request should use Java’s standard TLS certificate verification.

You should not disable certificate validation simply to make the Weather API request succeed.

If an HTTPS request fails because of certificate or trust-store problems, fix the Java runtime, operating-system certificates, corporate proxy configuration, or trust-store configuration causing the problem.

The Visual Crossing Weather API should be accessed using HTTPS.

Third-party HTTP libraries are optional

Older Java examples often used libraries such as Apache HttpComponents or OkHttp because earlier Java releases had a less convenient built-in HTTP API.

Modern Java includes:

java.net.http.HttpClient

which is sufficient for the requests shown in this tutorial.

If your application already uses Apache HttpComponents, OkHttp, Spring’s HTTP clients, or another networking framework, you can continue using that library.

The important parts remain the same:

  1. Construct the Timeline Weather API URL.
  2. Send an HTTPS GET request.
  3. Check the HTTP status.
  4. Parse the returned JSON.

There is no need to introduce an additional HTTP dependency solely to call the Visual Crossing Weather API.

Going further

Once your Java application can retrieve Timeline Weather API data, the same approach can support:

  • Historical weather analysis
  • Current-weather services
  • Hourly forecasts
  • Daily forecasts
  • Weather alerts
  • Business applications
  • Spring applications
  • Background services
  • Data pipelines
  • Agricultural systems
  • Transportation applications
  • Energy applications

The Visual Crossing Weather API provides programmatic access to historical weather, current conditions, forecasts, alerts, events, and other weather information.

For interactive weather-data exploration and downloads, use Visual Crossing Weather Data.

If you haven’t created an account yet, sign up for a free Visual Crossing account.

For detailed Timeline request parameters and response fields, see the Timeline Weather API documentation.

Summary

Modern Java applications can retrieve Visual Crossing Weather API data without requiring a third-party HTTP library.

The basic process is:

  1. Obtain a Visual Crossing Weather API key.
  2. Store the key outside the application source code.
  3. Build a Timeline Weather API URL containing the location and optional dates.
  4. Send the request using Java’s built-in HttpClient.
  5. Configure sensible connection and request timeouts.
  6. Check the HTTP status before processing the response.
  7. Parse the returned JSON using the JSON library already used by your application.
  8. Map daily, hourly, and current weather values into Java classes.

Because the Timeline Weather API provides historical, current, and forecast weather through a consistent endpoint, the same Java integration can support a wide range of weather-enabled applications.