Volley

Before Retrofit became the default choice, Google published its own HTTP library: Volley. It is older and less actively developed today, but it is still found in plenty of existing Android codebases, so it is worth knowing how to read and maintain, even if you would not necessarily pick it for a brand new project.

Volley was designed for many small, frequent requests – it was historically popular for simple JSON calls because of its built-in request queuing, prioritization, and response caching, all handled for you without extra setup.

Adding the Dependency

dependencies {
    implementation 'com.android.volley:volley:1.2.1'
}

The RequestQueue

Every Volley request is submitted to a RequestQueue – a single central object that manages all outgoing requests for your app, including their priority order, retry policy, and cache. Create one instance and reuse it (a singleton is the common pattern):

// VolleySingleton.java
public class VolleySingleton {
    private static VolleySingleton instance;
    private final RequestQueue requestQueue;

    private VolleySingleton(Context context) {
        requestQueue = Volley.newRequestQueue(context.getApplicationContext());
    }

    public static synchronized VolleySingleton getInstance(Context context) {
        if (instance == null) {
            instance = new VolleySingleton(context);
        }
        return instance;
    }

    public RequestQueue getRequestQueue() {
        return requestQueue;
    }
}

Fetching Recipes with JsonArrayRequest

Unlike Retrofit, which defines endpoints as a separate interface, Volley requests are built directly where you use them, with success and error listeners passed straight into the constructor:

// Inside RecipeListActivity.java
String url = "https://api.recipebox.example.com/recipes";

JsonArrayRequest request = new JsonArrayRequest(
        Request.Method.GET,
        url,
        null,
        new Response.Listener<JSONArray>() {
            @Override
            public void onResponse(JSONArray response) {
                List<Recipe> recipes = parseRecipes(response);
                adapter.setRecipes(recipes);
            }
        },
        new Response.ErrorListener() {
            @Override
            public void onErrorResponse(VolleyError error) {
                showError("Could not load recipes");
            }
        }
);

VolleySingleton.getInstance(this).getRequestQueue().add(request);
Explanation:
  • The success (Response.Listener) and error (Response.ErrorListener) callbacks are passed directly as constructor arguments – there is no separate interface to define first, unlike Retrofit's RecipeApiService.
  • Volley delivers both listeners on the main thread automatically, so UI updates here need no manual runOnUiThread() call.
  • requestQueue.add(request) is what actually schedules and executes the request.
  • A JsonObjectRequest works identically but for a single JSON object instead of an array – useful for fetching one recipe by id.

A Custom POST with StringRequest

When you need full control over the request body – for example, posting a new recipe as JSON – a StringRequest lets you override getBody() and getBodyContentType() directly:

String url = "https://api.recipebox.example.com/recipes";
String json = new Gson().toJson(newRecipe);

StringRequest request = new StringRequest(
        Request.Method.POST,
        url,
        response -> onRecipeCreated(),
        error -> showError("Could not save recipe")
) {
    @Override
    public byte[] getBody() {
        return json.getBytes(StandardCharsets.UTF_8);
    }

    @Override
    public String getBodyContentType() {
        return "application/json; charset=utf-8";
    }
};

VolleySingleton.getInstance(this).getRequestQueue().add(request);

Volley vs. Retrofit – An Honest Comparison

VolleyRetrofit
Setup for a simple JSON callVery quick – build a request, add to queueRequires defining an interface and building a Retrofit instance first
Type safetyWeak – you work with raw JSONObject/JSONArray unless you add your own parsing layerStrong – responses are typed Java objects via Gson
Caching & request priorityBuilt in, no extra configurationPossible, but needs to be added via OkHttp cache configuration
Active developmentMaintained, but sees far less active development and community activity todayActively maintained, the de facto standard for new Java/Kotlin Android projects
Scaling to many endpointsGets repetitive – each request rebuilds listeners inlineScales cleanly – add one method per endpoint to the interface
Recommendation: for a new RecipeBox-style project, prefer Retrofit (see android-retrofit-java.php) – it is the more modern, more widely recommended default. Learning Volley is still worthwhile so you can confidently read, debug, and maintain an existing codebase that already uses it.
Next up: JSON Parsing (org.json/Gson) – a closer look at parsing JSON manually versus letting Gson do it for you.